Java进阶:继承

 

 

1.Object类:所有类的超类

     ①

        Object类是所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确地指定超类,Object类就被认为是这个类的超类。可以使用Object类型的变量引用任何类型的对象。

        在Java中只有基本类型不是对象。

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

     ②equals方法:用于判断两个对象是否具有相同的引用

         在子类定义equals方法时,首先调用超类的equals。如果检验失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。

         下面实例展示了equals方法的实现机制:

public class Employee{
   public boolean equals(Object otherObject){
     if(this==otherObject)
      return true;
     if(otherObject==null)
      return false;
     if(this.getClass()!=otherObject.getClass())
      return false;
     Employee other=(Employee)otherObject;
     return name.equals(other.name)&& salary.equals(other.salary) && hireDay.equals(other.hireDay);
}

       

          ③相等测试与继承

             Java要求equals方法具有下面的特性:自反性、对称性、传递性、一致性和对于任意非空引用x,x.equals(null)应该返回false。

             下面给出编写一个完美的equals方法的建议:

  •         显示参数为otherObject,稍后需要将它转换成另一个叫other的bianl
  •         检测this与otherObject是否引用同一个对象
  •         检测otherObject是否为null,如果为null,返回fasle
  •         比较this与otherObject是否属于同一个类。如果equals的语义在每个子类有所改变,使用getClass来检测。如果所有子类都拥有统一的语义,就使用instanceof来检测
  •        将otherObject转换为相应的类类型变量
  •        开始对所有需要比较的域进行比较,使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配则返回true,否则返回false。

 

         ④hashCode方法

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

              由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。而字符串的散列码是由内容导出的。
             hashCode方法应该返回一个整数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。

            如果要组合多个散列码时可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Object.hashCode,并组合这些散列码。

            Equals与hashCode的定义必须一致。(如果x.equals(y)返回true,则x.hashCode()就必须与y.hashCode()返回的值相同)

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

 

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

            绝大多数的toString方法遵循这样的格式:类的名字,随后是一对方括号括起来的域值。

           随处可见toString方法的原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用toString方法,以便获得这个对象的字符串描述。

            Object类定义了toString方法,用来打印输出对象所属的类名和散列码。

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

             

 

2.对象包装器与自动装箱

            ①对象包装器

              所有的基本类型都有与之对应的类,这样的类称为包装类。对象包装类是不可变的,一旦构造了包装类就不允许更改包装在其中的值。同时包装类被定义为final,不能定义它的子类。

              自动装箱:

  ArrayList<Integer> list=new ArrayList();
  list.add(3);

/* 将自动变换成
  list.add(Integer.valueOf(3));
*/  

              自动拆箱:

int n=list.get(i);

/* 自动地转换成
  int n= list.get(i).intValue();
*/

              甚至可以在算术表达式中自动地装箱插箱。

              ==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域。

             关于自动装箱有几点需要注意:

                  1)由于包装类引用可以为null,所以自动装箱可能回抛出一个NullPointException异常

                  2)如果在一个表达式中混合使用Integer和Double类型,Integer就会拆箱提升为Double,再装箱为Double。

 

3.枚举类

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

     在比较两个枚举类型的值时,直接使用==,不需要调用equals方法。

     所有枚举类型都是Enum类的子类,继承了这个类的许多方法。

     1)其中最常用的就是toString方法,这个方法返回枚举常量名。

     2)toString的逆方法是静态方法valueOf。

     3)每个枚举类型都有一个静态方法values方法,返回一个包含全部枚举值的数组。

     4)ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。

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

Size[] values=Size.values();

 

4.反射

    反射的定义:能够分析类能力的程序称为反射。

            ①Class类

                Java运行时系统始终为所有的对象维护一个被称为 运行时 的类型标识。可以通过Class类访问这些信息。

                Object类中的getClass()方法终会返回一个Class类型的实例。一个Class对象将表示一个特定类的属性

              1)最常用的Class方法就是getName():这个方法返回类的名字。如果类在一个包中,包的名字也将作为类名的一部分。

              2)还可以调用静态方法forNmae()得到类名对应的Class对象。这个方法只有在className是类名或接口名才可以执行,否则将抛出一个checked exception。因此无论何时使用这个方法,都应该提供一个异常处理器。

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

              3)获得Class类对象: 如果T是任意的Java类型,T.class代表匹配的类对象。一个Class对象实际上表示的是一种类型,而这个类型未必一定是一种类。例如int。

              4)可以利用==运算符实现两个类对象比较的操作。newInstance():可以动态地创建一个类的实例。newInstance()调用默认的构造器初始化新创建的对象,如果这个类没有默认的构造器将会抛出一个异常。

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

         

            ②利用反射分析类的能力

                这一小节我们简要地介绍反射中最重要的内容:检查类的结构。

                在java.lang.reflect包中有三个类:Field、Method和Constructor分别描述类的域、方法和构造器。

             1)这三个类都有getName方法,用来返回项目的名称。

             2)Field类有getType方法,发挥描述域所属类型的Class对象

             3)Method和Constructor类有能够报告参数类型的方法。Method还有报告返回类型的方法。

             4)三个类还有getModifiers的方法,返回整型数值,用不同的位开关描述public和state这样的修饰符使用状况。                             5) java.lang.reflect包中有Modifier类中isPublic、isPrivate、isFinal判断方法或构造器是否为public、private和final。还可以使用Modifier.toString方法将修饰符打印出来。

         6)Class类中getFileds、getMethods、getConstructors方法将分别返回public域、方法和构造器数组。Class类的getDeclaredFields、getDeclaredMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器数组。

 

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

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

  •    获得相应的Class对象
  •    通过Class对象调用getDeclaredFields

                本节我们进一步查看数据域的实际内容。

             1)查看对象域的关键方法是Filed类中get方法。

Employee harry=new Employee("harry hacker",35000);
Class cl=harry.getClass();
Field f=cl.getDeclaredFields("name");
Object j=f.get(harry);

/*
 这段代码中存在问题。
 由于name为私有域,get方法会抛出IllegalAccessException异常
 除非拥有权限,否则Java安全机制只允许查看有哪些域而不允许查看这些域的值
*/

             2)为了达到覆盖访问控制,我们可以调用Filed、Method或Constructor对象的setAccessible方法

                   setAccessible方法是AccessibleObject类的方法,它是Filed、Method和Constructor类的公共超类。这个特性是为了调试、持久存储和相似机制提供的。

    f.setAccessible(true);

             3)如果我们需要查看salary域,它属于Double类型,而非对象。要解决这个问题,可以使用Filed类中的getDouble方法,也可以使用get方法。此时反射机制自动地将这个域值打包到相应的对象包装类中。

             4)可以获得就可以设置。

        f.set(obj,value);
       //将obj对象的f域设置为新值value

                              

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

                java.lang.reflect包中的Array类允许动态地创建数组。

   Employee[] a=new Employee[100];
   a=Arrays.copyOf(a,2*a.length);

               编写一个通用的copyOf方法,需要能够创建与原数组类型相同的新数组。为此,需要使用到lang.reflect包中的Array类的一些方法。其中最为关键的是Array类中的静态方法newInstance,它能够构造新数组。

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

               为了能够实际地运行,需要获得新数组的长度和元素类型。可以调用Array.getLength(a)获得数组长度。而要获得新数组的类型需要进行一下工作:

  • 首先获得a数组的类对象
  • 确认它是个数组
  • 使用Class类的getComponentType方法确定数组对应的类型
 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)在Method类中有一个invoke方法,它允许调用包装在当前Method对象总的方法

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

              如果ml代表Employee类的getName方法,下面语句表示了如何调用这个方法。如果返回类型是基础类型,invoke方法会返回其包装器类型。

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

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

             2)如何得到Method对象

  • 可以调用getDeclaredMethods方法,对返回的Method对象数组进行查找
  • 也可以调用Class类的getMethod犯法得到想要的方法。
 Method getMethod(String name,Class...parameterTypes);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值