java核心技术 第五章 继承

一、类、超类、和子类

相对于Employee,Manager完成业务后有奖金。这种情况下使用继承。

class Manager extends Employee{

}

在Manager类中,增加了一个用于存储奖金信息的域,以及用于设置这个域的方法。

public class Manager extends Employee
{
   private double bonus;

   .....

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

Employee对象是无法调用Manager中的set方法的。

Manager类继承了Employ类的所有域和方法。Manager 类的对象可以调用Employ中的方法。

扩展超类的时候,仅需指出子类与超类的不同。在设计类的时候,将通用的方法放在超类中,将特殊用途的方法放在子类中,这种方法十分普遍。

超类中的某些方法对子类不适用。如Manager类计算薪水的时候需要加上奖金,这时候需要覆盖(override)超类中的方法。

public class Manager extends Employee
{

   public double getSalary()
   {

      return salary + bonus;// don't work
   }


}

子类不能访问父类的私有域,只有通过父类的提供的公共接口才可以访问

public class Manager extends Employee
{

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


}

子类可以通过super关键字调用父类中的方法。

注意:this是对当前对象的引用,可以赋值给其他对象,而super不是对当前对象的引用,只是告诉编译器调用超类方法的特殊关键字,不能赋值给对象。

由于Manager类的构造器不能访问Employee类的私有域,所以必要调用Employee类的构造器对私有域进行初始化。通过super关键字可以调用超类构造器。super调用构造器的语句必须是子类构造器语句的第一条。

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

如果子类的构造器没有显示地调用超类的构造器,那么则会自动调用超类的默认构造器(无参构造器)。如超类没有无参构造器,子类又没有显示地调用超类中的其它构造器,Java编译器就会报错。

注意:关键字this有两个用途:1.引用隐式参数。2.调用该类的其它构造器。关键字super也有两个用途:1.调用父类的构造方法。2.调用父类的构造器。调用构造器时,这两个关键字只能作为另一个构造器的第一条语句出现。构造参数即可以传递给本类(this)的其它构造器,也可以传递给父类构造器(super);

Employee 对象变量即可以引用Employee对象,也可以引用子类Manager对象。调用getSalary方法时,如果引用Emmployee对象则调用Employee对象的调用getSalary方法。,如果引用Manager对象则调用Manager对象的调用getSalary方法。

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

public class Employee
{
   private String name;
   private double salary;
   private Date hireDay;

   public Employee(String n, double s, int year, int month, int day)
   {
      name = n;
      salary = s;
      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
      hireDay = calendar.getTime();
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public Date getHireDay()
   {
      return hireDay;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}


public class Manager extends Employee
{
   private double bonus;

   /**
    * @param n the employee's name
    * @param s the salary
    * @param year the hire year
    * @param month the hire month
    * @param day the hire day
    */
   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;
   }
}

package inheritance;


/**
 * This program demonstrates inheritance.
 * @version 1.21 2004-02-21
 * @author Cay Horstmann
 */
public class ManagerTest
{
   public static void main(String[] args)
   {
      // construct a Manager object
      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);

      Employee[] staff = new Employee[3];

      // fill the staff array with Manager and Employee objects

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

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

1.1 继承层次

Java不支持多继承,一个类只能继承一个父类。由一个超类派生出的所有子类组成的集合叫做继承层次。

1.2 多态

判断是否应该设计为继承关系的简单准则是:(is-a)准则,即子类对象也是超类对象。Manager 对象也是Employee对象。但Employee对象不是Manager对象。父类对象不能调用子类对象中新声明的其它方法,否者会报错。

 Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
staff[0] = boss;
staff[0].setBonus(5000);//Error!

将父类对象赋值给子类对象非法

Manager m=staff[i];//Error!

在Java语言中,子类数组引用转换成父类数组引用不需要强制类型转换

1.3 动态绑定

1.4 阻止继承:final类和方法

阻止人们利用某个类定义子类,就用final关键字修饰该类

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

阻止子类覆盖该方法:

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

域被声明为final之后,调用构造方法之后不允许对final域进行更改。类被声明为final类后,所有方法都被自动声明为final,而不包括域。

String是final类。

1.5 强制类型转换

将一个类型强制转换成另外一个类型的过程被称为类型转换。

double x = 3.405;
int nx =(int)x;//舍弃小数部分

对象的强制类型转换

Manager boss = (Manager) staff[0];

进行类型转换的唯一原因是:在短暂忽略对象的实际类型后,使用对象的全部功能。

将子类引用赋值给超类变量是允许的。将超类引用赋值給 子类变量时,需要强制类型转换。

直接转换时如果是被转换引用原始对象类型不是转换对象有可能报错

Manager boss=(Manager)staff[1] //ClassCastException

instanceof 可以对被转换类型进行判断

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

综上所述:
- 只能在继承层次内进行类型转换。
- 在将超类转换成子类之前。应该使用 instanceof 对象。

1.6 抽象类

从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。

例如 Student 和 Employee都有name,因此将name实例域和getName方法放在他们的父类 Person类中。

需要增加一个getDescription方法,它可以返回对一个人的简介。
Student和Employee类可以实现。那么Persion类应该提供什么内容?空字符串 ?最好使用abstract方法:

public abstract String getDescription();

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

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

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

public abstract class Person
{
   public abstract String getDescription();
   private String name;

   public Person(String n)
   {
      name = n;
   }

   public String getName()
   {
      return name;
   }
}

抽象方法充当着占位的角色,它们的具体实现在子类中。扩展抽象类可以有两种选择。一种是在子类中定义部分抽象方法或者抽象方法也不定义,这样就必须将子类也标记为抽象类。另另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。

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

抽象类不能被自身实例化,但可以创建一个具体的子类对象。

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

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

抽象类变量只能调用自身声明过方法,而不能调用子类另外新声明的方法。

public abstract class Person
{
   public abstract String getDescription();
   private String name;

   public Person(String n)
   {
      name = n;
   }

   public String getName()
   {
      return name;
   }
}


public class Employee extends Person
{
   private double salary;
   private Date hireDay;

   public Employee(String n, double s, int year, int month, int day)
   {
      super(n);
      salary = s;
      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
      hireDay = calendar.getTime();
   }

   public double getSalary()
   {
      return salary;
   }

   public Date getHireDay()
   {
      return hireDay;
   }

   public String getDescription()
   {
      return String.format("an employee with a salary of $%.2f", salary);
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}


public class Student extends Person
{
   private String major;

   /**
    * @param n the student's name
    * @param m the student's major
    */
   public Student(String n, String m)
   {
      // pass n to superclass constructor
      super(n);
      major = m;
   }

   public String getDescription()
   {
      return "a student majoring in " + major;
   }
}


/**
 * This program demonstrates abstract classes.
 * @version 1.01 2004-02-21
 * @author Cay Horstmann
 */
public class PersonTest
{
   public static void main(String[] args)
   {
      Person[] 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());
   }
}

1.7 受保护访问

Java用于控制可见性的4个访问修饰符

1.仅对本类可见 - private。
2.对所有类可见 - public。
3.对本包和所有子类可见- protected。
4.对本包可见-默认,不需要修饰符。

二、Object: 所有类的超类

Object 类是Java所有类的始祖。没有明确指出超类,Object类就是超类。

除了基本数据类型外,数组以及对象类都扩展于Object

2.1 equals 方法

Object 类的 equals 方法用于检测一个对象是否等于另一个对象。在Object类中,这个方法是比较两个对象是否具有相同的引用。
只比较引用是没有意义的,人民经常比较他们的域,如果域值相等,那么这两个对象就相等。

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;

      return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
   }

  ....
}

为了防止Employee 类 name 域值为空,使用Objects.quals(A,B)来比较两个对象是否相等。

在子类中定义equals方法时,首先应当调用超类的equals。如果超类的域相等,就需要比较子类的域。

public class Manager extends Employee
{
....

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

....

}

2.2 相等测试与继承

如果隐式和显示的参数不属于同一个类,equals如何处理。

采用 instanceof 进行比较会招致麻烦,尽量不采用。

下面是编写一个完美equals 方法建议

1.显式参数命名为 otherObject ,稍后需要将它转化成另一个叫做 other 的变量。

  1. 检测this与otherObject是否引用同一个对象
if(this == otherObject) return  true;
  1. 检测otherObject是否为null,如果为null返回false。
if(otherObject == null) return false;
  1. 比较this与otherObject是否属于同一个类。如果equles语句在每个子类中有所改变就用getClass检测
 if (getClass() != otherObject.getClass()) return false;

5.将otherObject转换为相应的类类型变量:

  Employee other = (Employee) otherObject;

6.对所有需要比较的域进行比较。使用==比较基本类型,使用 equals 比较对象域。如果所有的域都匹配,就返回true。

  return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);

java 1.7 Objects的equals方法:

 public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

如果在子类中重新定义equals,就要在其中包含调用super.equals(other)

注意:对于数组类型的域,何以使用静态的Arrays.equals(),检查相应的域是否相等。

2.3 hashCode 方法

散列码(hash code)是由对象导出的一个整形值。如果两个对象不同,x.hashCode()和y.hashCode()基本上会不同。

String类散列码的计算方式:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

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

hashCode方法应该返回一个整形数值,并合理地组合实例域的散列码,以便让不同散列码更均匀。

Employee 散列码:

class Employee
{
   public int hashCode(){
       return 7*name.hashCode()+11*new Double(salary).hashCode()+13*hireDay.hashCode();
   }    
}

Java 7 使用null安装的Objects.hashCode。如果其参数为null, 这个方法返回0,否者返回对参数调用hashCode的结果:

 public int hashCode()
   {
      return Objects.hash(name, salary, hireDay); 
   }



Objects的hash方法

  public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

Arrays.hashCode()方法:

 public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

如果存在数组类型的域,可以使用Arrays.hashCode()方法。

2.4 toString()方法

Object含有一个toString()方法,默认输出对象的类名和散列码。
正常情况下,应当以下形式输出。

public class Employee
{

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

}

子类的toString可以调用父类的toString方法,然后再输出自己的域

例:

public class Employee
{
   private String name;
   private double salary;
   private Date hireDay;

   public Employee(String n, double s, int year, int month, int day)
   {
      name = n;
      salary = s;
      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
      hireDay = calendar.getTime();
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public Date getHireDay()
   {
      return hireDay;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   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 Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
   }

   public int hashCode()
   {
      return Objects.hash(name, salary, hireDay); 
   }

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

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

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

   public int hashCode()
   {
      return super.hashCode() + 17 * new Double(bonus).hashCode();
   }

   public String toString()
   {
      return super.toString() + "[bonus=" + bonus + "]";
   }
}



public class EqualsTest
{
   public static void main(String[] args)
   {
      Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee alice2 = alice1;
      Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);

      System.out.println("alice1 == alice2: " + (alice1 == alice2));

      System.out.println("alice1 == alice3: " + (alice1 == alice3));

      System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));

      System.out.println("alice1.equals(bob): " + alice1.equals(bob));

      System.out.println("bob.toString(): " + bob);

      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);
      System.out.println("boss.toString(): " + boss);
      System.out.println("carl.equals(boss): " + carl.equals(boss));
      System.out.println("alice1.hashCode(): " + alice1.hashCode());
      System.out.println("alice3.hashCode(): " + alice3.hashCode());
      System.out.println("bob.hashCode(): " + bob.hashCode());
      System.out.println("carl.hashCode(): " + carl.hashCode());
   }
}

三、泛型数组列表

C语言编译时必须确定其大小。而Java语言允许运行时确定其大小:

Employee[] staff = new Employee[actualSize]

ArrayList是采用类型参数(type parameter)的泛型类(generic class)

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

Java1.7可以省略右边的类型参数,编译器会检测变量、方法参数类型,类型参数放在<>中。

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

add添加

staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15));

ArrayList 对象内部维护着一个Object对象数组,初始容量为10,添加容量不够时1.5倍增加。

确保数组容量

    staff.ensureCapacity(100);

初始化时就创建构造器

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

返回数组列表中实际元素大小。

 staff.size();

确定数组元素列表不可变后,调整区域为数组元素实际大小。

staff.trimToSize();

3.1 访问数组类表中的元素

 staff.get(i);//i小于等于数组元素大小。

set数组中的某个元素。

staff.set(0, employee);


   set前会进行rangeCheck  
 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

toArray(T[])方法可以把数组类表中的元素拷贝到一个数组中

 Employee []  e=new Employee[100];
 staff.toArray(e);

用foreach访问数组的每个元素。

 // raise everyone's salary by 5%
      for (Employee e : staff)
         e.raiseSalary(5);

public class ArrayListTest
{
   public static void main(String[] args)
   {
      // fill the staff array list with three Employee objects
      ArrayList<Employee> staff = new ArrayList<>();
      staff
      staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15));
      staff.add(new Employee("Harry Hacker", 50000, 1989, 10, 1));
      staff.add(new Employee("Tony Tester", 40000, 1990, 3, 15));

      // raise everyone's salary by 5%
      for (Employee e : staff)
         e.raiseSalary(5);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay="
               + e.getHireDay());
   }
}

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

由Object类型转换具体类型时,会类型转换出错。使用(@SupperssWarings(“uncheckedcd”)b标记防止出错)

四、对象包装器与自动装箱

基本类型都有一个与之对应的类。这些类被称为包装器(wrapper)。 对应包装器是final类,一旦构建 就不可变。

ArrayList不支持基本数据类型。添加基本数据类型时会进行自动装箱(autoboxing),将Integer类赋值给基本数值类型,会进行自动拆箱。

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

包装器类型比较时用equals方法。

五、参数可变的方法

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

public class PrintStream printf(String fmt,Object ... args)

… 表示接受任意数量的对象,实际上Object … args转换成 Object[] 对象数组。

六、枚举类

enum Size{
    SMALL("S"),MEDIUM("M"),LARGE("L"), EXTRA_LARGE("XL");}

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

所有枚举类都是Enum类的子类,并继承其中的方法。例如toString(),方法,返回枚举常量名。

  Size.SMALL.toString()//返回SMALL

toString()的逆方法是静态方法 valueof()

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

包含全部枚举值的数组

 Size [] values=Size.values();

例:enums

import java.util.Scanner;

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;
}
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 _.");      
    }

}

七、反射

能够分析类的能力为反射(reflective).

7.1 Class类

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


 Employee e;
 ....
 Class cl = e.getClass();

如同用一个Employee对象表示一个特定的雇员属性一样,一个Class对象将表示一个特定类的属性。最常用的Class方法是getName(),该方法将返回类的名,如果该类包含在包中,则包名做为类名的一部分输出。例:

package com.lengyu.reflecttest;

import java.util.Date;

public class ReflectTest {
    public static void main(String []arg){
        Date d = new Date();
        Class cl = d.getClass();
        String name = cl.getName();
        System.out.println("cl 的类名是:"+name);
    }
}

输出为:
这里写图片描述

cl 的类名是:java.util.Date

如果类名保存在字符串中,并且在运行中可以改变字符串的值,这可以通过Class类的静态方法forName(String className)来获得类名所对应的Class对象,当然前提是存在className对应的类名或者接口,否者会抛checked exception (已检查异常)。所以使用该方法时应当尽量捕获异常。可以在程序开始通过调用Class.forName手工地加载其他的类提高启动速度。

package com.lengyu.reflecttest;

import java.util.Date;

public class ReflectTest {
    public static void main(String []arg){

        String className = "java.util.Date";
        Class cl;
        try {
            cl = Class.forName(className);
            System.out.println("cl 的类名是:"+cl.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出为:

这里写图片描述

cl 的类名是:java.util.Date

第三种获取类的方法比教简单,如果 T 是任意的Java类型,T.class表示匹配的类的对象。

package com.lengyu.reflecttest;

import java.util.Date;

public class ReflectTest {
    public static void main(String []arg){

        Class cl1= Date.class;
        Class cl2= byte.class;
        Class cl3= char.class;
        Class cl4= int.class;
        Class cl5= float.class;
        Class cl6= double.class;
        Class cl7= boolean.class; 
        Class cl8= String[].class;
        Class cl9= Double[].class;
        System.out.println("cl1的类名是:"+cl1.getName());
        System.out.println("cl2的类名是:"+cl2.getName());
        System.out.println("cl3的类名是:"+cl3.getName());
        System.out.println("cl4的类名是:"+cl4.getName());
        System.out.println("cl5的类名是:"+cl5.getName());
        System.out.println("cl6的类名是:"+cl6.getName());
        System.out.println("cl7的类名是:"+cl7.getName());
        System.out.println("cl8的类名是:"+cl8.getName());
        System.out.println("cl9的类名是:"+cl9.getName());
    }

}

输出为:
这里写图片描述

cl1的类名是:java.util.Date
cl2的类名是:byte
cl3的类名是:char
cl4的类名是:int
cl5的类名是:float
cl6的类名是:double
cl7的类名是:boolean
cl8的类名是:[Ljava.lang.String;
cl9的类名是:[Ljava.lang.Double;

虚拟机为每个类型管理一个Class对象,可以通过==号实现类的对比操作if(e.getClass()==Employee.class)

可以通过Class类的newInstance()静态方法来创建一个类的实例,newInstance()方法调用类默认构造器(无参数构造器)初始化新创建的对象。

package com.lengyu.reflecttest;

import java.util.Date;

public class ReflectTest {
    public static void main(String []arg){
        String s = "java.util.Date";
        try {
            Object m = Class.forName(s).newInstance();
            System.out.println(((Date)m).toString());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

输出为:
这里写图片描述

Mon Apr 09 12:20:32 CST 2018

如果需要以这种方式向希望按名称创建的类的构造器提提供参数,就不能用Class对象的newInatance()方法;而必须用Constructor类中的newInstance方法。

反射机制最重要的内容-检查类的结构
在 java.lang.reflect包中有三个类Field、Method、Constructor分别描述类的域、方法和构造器。三个类中的getName()方法分别返回域名、方法名、和构造器名。Method和Constructor类中的getParameterTypes()按声明顺序返回参数对象数组。Method中的getReturnType()返回返回类型,这三个类还有一个叫做getModifiers的方法,它将返回一个整数数值,用不同的位开关描述public和static这样的修饰符使用状况,可以使用Modifier类中的isPulic(int)、isPrivate(int )等方法来分析方法是否是public、private,可以用Modifier.toString( int),方法将修饰符打印出来。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public 域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但 不包括超类的成员

例 :reflection/Reflection.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 + ";");
      }
   }
}

输出:
这里写图片描述

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

在第一节中,我们知道了获取Class类的三种方法(e.getClass()、Class.forname()、T.class),在第二节中我们已经知道如何查看任意对象的数据域名称和类型(1.获得对应的Class对象,2.通过Class对象调用getDeclaredFields.),本节,将进一步查看数据域的实际内容。

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

String s = "java.util.Date";
        try {
            Class cl = Class.forName(s);
            Field field =cl.getDeclaredField("serialVersionUID");
            //field.setAccessible(true);
            System.out.println(field.get(null));//"serialVersionUID"是Date 的私有静态域,故传入的对象为null
        } catch (Exception e) {

            e.printStackTrace();
        }
java.lang.IllegalAccessException: Class reflection.ReflectionTest can not access a member of class java.util.Date with modifiers "private static final"
    at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
    at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
    at java.lang.reflect.Field.get(Unknown Source)
    at reflection.ReflectionTest.main(ReflectionTest.java:19)

“serialVersionUID”是Date的私有静态域,所以get方法会抛一个非法访问异常,只有利用get方法才能得到可访问域的值。除非拥有访问权限,否者Java安全机制只允许查看任意对象有那些域,而不允许读取它们的值。

反射机制的默认行为受限于java的访问控制。然而如果一个java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。
setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constrocter的公共超类。这个特性是为调试、持久存储和相似机制提供的。
get()返回Object对象,getString(),getInt(),直接返回相应类型。
可以调用set(obj,value)设相应的值。

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



/**
 * 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(squares.getClass());
      System.out.println(new ObjectAnalyzer().toString(squares));
   }
}

输出:

java.util.ArrayList[elementData=class java.lang.Object[]{java.lang.Integer[value=1][][],java.lang.Integer[value=4][][],java.lang.Integer[value=9][][],java.lang.Integer[value=16][][],java.lang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]

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

利用 Arrays 拷贝对象数组。

   Date []  f = new Date[10] ;
       f  = Arrays.copyOf(f, 2*f.length);

下面方法会抛出异常:

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

下面是一个正常的方法:

  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[]是一个对象,而不是对象数组。所以返回类型为Object而不是Obejct[]

例: 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)
   {
       Date []  f = new Date[10] ;
       f  = Arrays.copyOf(f, 2*f.length);
      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;
   }
}

输出:

[1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
[Tom, Dick, Harry, null, null, null, null, null, null, null]
The following call will generate an exception.
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
    at arrays.CopyOfTest.main(CopyOfTest.java:26)

7.6 调用任意方法

从表面上看,java没有提供方法指针,即将一个方法的存储地址传给另一个方法,以便第二个方法能够随后调用它。
Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法,invoke方法的签名是:

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

第一个参数是隐式参数,其余的对象提供了显示参数,对于静态方法,第一个参数可以被忽略,即可将它设为null。
假设用ml代表Employee Employee类的getName方法,下面这条语句显示了如何调用这个方法

String n = (String) ml.invoke(harry);

invoke返回的是一个Object对象,如果原方法返回的是基本数据类型,则invoke会返回一个Object对象需要转换成基本类型对应的引用类型,然后进行自动拆包来完成。

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

可以通过 getDeclaredMethod(String name,Class<?> ... parameterTypes)或者 getMethod(String name,Class<?> ... parameterTypes)来获得Method对象

methods/ MethodPointerTest.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();
         }
      }
   }
}

输出:

public static double methods.MethodTableTest.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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值