Java核心技术 第五章 继承

类、超类、子类:

子类方法不能直接访问超类的私有域。

this和super并非引用,不能将其赋给另一个对象变量。

super在构造器中的应用:

public Manager(String n, double s, int year, int month, int day ) {

  super(n, s, year, month, day) ;

  bonus = 0 ;

}

使用super调用构造器的语句必须是子类构造器的第一条语句。

如果子类构造器没有显示的调用超类的构造器,则将自动的调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显示的调用超类的其他构造器,则Java编译器会报错。

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

多态:

每个经理都是雇员,是is-a关系,所以经理可以继承于雇员。

动态绑定:

对象调用对象方法的过程:

  1. 编译器查看对象的声明类型和方法名。
  2. 接下来,编译器将查看调用方法时提供的参数类型。
  3. 如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确的知道应该调用哪个方法,我们将这种调用方式成为静态绑定。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
  4. 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。x类型为D,D中无对应方法,则在超类中查找对应方法。

在覆盖一个方法时,子类方法不能低于超类方法的可见性。(否则编译不能通过)

阻止继承:final类和方法

如果在定义类的时候使用了final修饰符就表明这个类是final类,不允许被继承。

类中的方法被声明为final,则该方法在子类中不能被覆盖。

将类或方法声明为final的主要目的是:确保他们不会在子类中改变语义。

强制类型转换:

将一个子类的引用赋给一个超类变量,编译器是允许的。但是将一个超类的引用赋给一个子类变量,必须进行类型转换。

Manger boss = (manager) staff[1] ;

会在运行时发生错误,抛出ClassCastException异常。

在进行类型转换前,应先查看是否能够成功转换。

if(staff[1] instanceofr Manager) {

  boss = (manager) staff[1] ;

}

如果类型转换不可能成功,编译器就不会进行这个转换。例:

Date c = (Date) staff[1] ;  //将会产生编译错误, 因为Date不是Employee的子类

综上所述:

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

抽象类:

abstract class Person {

  Private String name ;

  public Person(String n) {

    name = n ;

  }

  public abstract String getDescription() ;

  public String getName() {

    return name ;

  }

}

//Employee 和 Student 继承Person

Person[] people = new Person[2] ;

people[0] = new Employee(...) ;

people[1] = new Student(...) ;

for(Person p : people) {

  System.out.println(p.getName() + "," + p.getDescription) ;

}

p.getDescription()合法,变量p永远不会引用Person对象,而是引用Employee或Student子类中的getDescription()方法。

受保护访问:

4个访问修饰符:

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

protected:与基类不在同一个包中的子类,只能访问自身从基类继承而来的受保护成员,而不能访问基类实例本身的受保护成员 。

Object:所有类的超类:

Object类是所有类的始祖,在Java中每个类都是由它扩展而来的。

equals方法:

Object类中的equals方法判断两个对象是否有相同的引用。对大多数类来说,并没有意义,所以为了比较两个类的状态是否相同,需要重写equals方法。

Java语言规范要求equals具有下面特性:

  1. 自反性:非空引用x,x.equals(x)应返回true
  2. 对称性
  3. 传递性
  4. 一致性:反复调用,结果不变
  5. 对于任意非空引用x,x.equals(null)应该返回false。

注意:

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

编写equals方法建议:

  1. 显示参数命名为otherObject,稍候需要将它转换为另一个叫other的变量。
  2. 检测this与otherObject是否引用同一个对象:if(this == otherObject) return true ;
  3. 检测otherObject是否为null,如果为null,返回false
  4. 比较this与otherobject是否属于同一个类。如果equals的语义在每个子类中有所改变,就用getClass检测,if(fetClass() != otherObject.getClass()) return false ;否则使用instanceof检测,if(!(otherObject instanceof ClassName)) return false ;
  5. 将otherObject转换为相应的类型变量:ClassName other = (ClassName)otherObject ;
  6. 现在开始对所有需要比较的域进行比较了,使用==比较基本类型,使用equals比较对象域。

hashCode方法:

散列码是由对象导出的一个整形值。

字符串的散列码是由内容导出的,因此内容相同的字符串,散列码相同。String类重写了hashCode方法。

equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。

如果存在数组类型的域,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成。

toString方法:

设计子类的程序员也应该定义自己的toString方法,并将子类域的描述添加进去。如果超类使用了getClass().getName(),那么子类只要调用super.toString()就可以了。

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

int[] luckynumbers = {1,3,5,7,9} ;

String s = Arrays.toString(luckyNumbers) ;

结果:[1,3,5,7,9]

java.lang.Class

String getName()   //返回这个类的名字

Class getSuperclass()  //以Class对象的形式返回这个类的超类信息

泛型数组列表:

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

Java7中可以省略后面的<Employee>,ArrayList<Employee> staff = new ArrayList<>() ;

staff.add(new Employee("Harry Hacker", ...)) ;

staff.ensureCapacity(100) ;

new ArrayList(100)  //capacity is 100

staff.size()  //返回数组列表中实际包含的元素数目,等价于数组a的a.length

void trimToSize() //将数组列表的存储容量削减到当前尺寸

访问数组列表元素:

staff.set(i, harry) ;  //只有当i位置存在元素时才能使用set方法

Employee e = staff.get(i) ;

Employeee = staff.remove(n) ; //位于这个位置之后的所有元素都向前移动一个位置,并且数组的大小减一。i必须在0~size() -1 之间

对象包装器与自动装箱:

基本类型对应的包装器类:Integer,Long,Double,Float,Short,Byte,Character,Void, Boolean。(前六个派生于公共的超类Number)

对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时对象包装器类还是final,因此不能定义它们的子类。

<>中的类型参数不允许是基本类型,因此不允许写成:ArrayList<int>,可以写成Arraylist <Integer> list = new ArrayList<>() ;

Java se 5.0 后, list.add(3)自动转换为list.add(Integer.valueOf(3)) 这种变换称为自动装箱。

相反,将一个Integer对象赋给一个int值时,将会自动进行拆箱。

int n = list.get(i) ; //翻译成 int n = list.get(i).intValue() ;

算术表达式中也能进行自动的拆箱装箱:

Integer n = 3 ;

n++ ;

java.lang.Integer 1.0

static int parseInt(String s)

static int parseInt(String s, int radix)

java.text.NumberFormat 1.1

Number parse(String s)

参数数量可变的方法:

public static double max(double... values) {

  double largest = Double.Min_VALUE ;

  for (double v : values) if(v > largest) largest = v ;

  return largest ;

}

调用:double m = max(3.1, 40.4, -5)

反射: 

反射机制可以用来:

  1. 在运行时分析类的能力。
  2. 在运行中查看对象,例如,编写一个toString方法供所有类使用。
  3. 实现通用的数组操作代码
  4. 利用Method对象,这个对象很像C++中的函数指针

Class类:

Employee e ;

Class cl = e.getClass() ;

e.getClass().getName() ;

还可以调用静态方法forName获得类名对应的Class对象。

String className = "java.util.Date" ;

Class cl  = Class.forName(className) ;

className必须是类名或接口名,否则在运行时会抛出已检查异常。使用该方法时,应提供一个异常处理器。

获得Class对象的第三种方法:

如果T是任意的Java类型,T.calss将代表匹配的类对象。

Class cl1= Date.class ;

Class cl2 = int.class ;

Class cl3 = Double[].class ;

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

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

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

e.getClass.newInstance() ;  //创建一个与e相同类型的实例。newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认构造器,就会抛出一个异常。

String s = "java.util.Date" ;

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

如果需要以这种方式向希望按名称创建的类的构造器提供参数,就不要使用上面那条语句,而必须使用Constructor类中的newInstance方法。

利用反射分析类的能力:

在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。这三个类都有一个叫getName的方法,用于返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。这三个类还有一个叫getModifiers的方法,它将返回一个整数数值,用不同的位开关描述public和static这样的修饰符使用情况。另外,可以使用Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。另外可以利用Modifier.toSting方法将修饰符打印出来。

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

Constructors[] constructors = cl.getDeclaredConstructors() ;

Method[] methods = cl.getDeclaredMethods() ;

Field[] fields = cl.getDeclaredFields() ;

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

在编写程序使,如果知道想要查看的域名和类型,查看指定的域是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的对象域。

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

Employee harry = new Employee("Hary Hacker", 35000, 10, 1, 1989) ;

Class cl = harry.getClass() ;

Field f = cl.getDeclaredField("name") ;

Object v = f.get(harry) ;

由于name是私有域,所以get方法会抛出一个IllegalAccessException。

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

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

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

get方法还有一个需要解决的问题。name域是一个String,可以作为Object返回。若要返回double类型的域,需要使用Field类中的getDouble方法。阈值将自动打包为Double。

调用f.set(obj, value)可以将obj对象的f域设置成新值。

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

Employee[] a = new Employee[100] ;

a = Arrays.copyOf(a, 2*a.length) ;

copyOf方法的实现:

第一种方法:

public static Object[] badCopyOf(Object[] a, int newLength]) {

  Object[] newArray = new Object[newLength] ;

  System.arrayCopy(a, 0, newArray, 0, Math.min(a.length, newLength)) ;

  return newArray ;

}

由于方法内新建的是Object[]数组,最终将不能转变为Employee[]数组,所以该方法不可行。

改进版:

public static Object goodCopyOf(Object a, int newLength) {

  Class cl = a.getClass() ;

  if (!cl.isArray()) return null ;

  Class componentType = cl.getComponentType() ;

  int length = Araay.getLength(a) ;

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

  System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength)) ;

}

int[] a = {1,2,3,4,5} ;

a = (int[]) goodCopyOf(a, 10) ;

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

调用任意方法:

Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:

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

第一个参数是隐式参数,其余的对象提供了显式参数。

对于静态方法,第一个参数可以被忽略,即可以将它设置为null。

假设m1代表Employee类的getName方法,下面这条语句显示了如何调用这个方法:

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

如果返回的是基本类型,invoke方法会返回其包装器类型。

假设m2表示Employee类的getSalary方法,那么返回的对象实际上是一个Double,必须相应地完成类型转换。可以使用自动拆箱将它转换为一个double:

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

可以通过调用getDeclaredMethod方法,然后对返回的Method对象数组进行查找,知道发现想要的方法为止。也可以通过调用Class类中的getMethod方法得到想要的方法。由于存在相同名字的方法,因此,必须提供想要的方法的参数类型。getMethod的签名是:

Method getMethod(String name, Class... parameterTypes)

例:

Method m1 = Employee.class.getMethod("getName") ;

Method m2 = Employee.class.getMethod("raiseSalary", double.class) ;

如果在调用方法的时候提供了一个错误的参数,那么invoke方法将会抛出一个异常,另外invoke的参数和返回值必须是Object类型的。有鉴于此,建议在必要的时候才使用Method对象,而最好使用接口和内部类。

继承设计的技巧:

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

Java核心技术 第五章 继承的相关文章

Java核心第五章继承

5.1类 超类(父类.基类)  子类(派生类) 使用关键字extends来继承 对于子类想访问父类的私有域,则必须要借助公有接口,在父类中的公有方法正是这样的接口 为了防止子类定义了与父类一样的成员函数,则可以使用特定关键字super来解决: super.父类的成员函数    super与引用的概念不同 ,它只是一个指示编译器调用超类方法的特殊关键字,并不能将super赋给另一个对象变量 在子类中可以增加域 增加方法 或者覆盖超类的方法,然而绝对不能删除继承的任何域和方法 一个对象变量可以指示多

Java 第十二章 继承 笔记

Java 第十二章  继承 笔记 一.使用继承:     1)方便修改代码     2)减少代码量 二.super 继承object 类:super 访问父类的无参构造:super 指的是object 的无参构造.     例:子类调用父类:super.属性 / super.方法    注意:子类不能继承父类私有属性:得用set.get方法来调用:    super只能写在代码块的第一句:super只能调用非私有的方法:    super只能出现在子类的方法和构造方法中. 三.不能被继承的父类成

Java核心技术 第六章 接口和内部类

Java核心技术  第六章  接口与内部类 接口: 任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整数数值. 在Java SE 5.0中,Comparable接口已经改进为泛型类型. 接口中所有的方法自动的属于public.因此,在接口中声明方法时,不必提供关键字public. 接口中决不能含有实例域,也不能在接口中实现方法. 要让一个类使用排序服务,必须让它实现compareTo方法,因此必须实现Comparable

[Java学习笔记] Java核心技术 卷1 第五章 继承

第5章 继承 利用继承,可以基于已存在的类构造一个新类.继承已存在的类就是复用(继承)这些类的方法和域.还可以在此基础上添加新的方法和域. 反射. 5.1 超类子类 使用extends构造一个派生类 class Manager extends Employee { 添加方法和域 覆盖:重写一些基类中不适合派生类的方法 } 所有的继承都是公有继承.即所有的公有成员和保护成员都保持原来的状态,基类的私有成员仍然是私有的.不能被派生类的成员访问. (C++中私有继承或保护继承会将 公有成员和保护成员都

Java核心技术第三章----Java的基本程序设计结构重难点总结

最近在看Java核心技术这本书,这里对第三章个人认为的重难点做一个总结.方便以后回顾,个人能力有限,本人菜鸟,大神勿喷,请大家多多指教. 一.位运算符 指定 A = 66(0100 0010); B = 22 (0001 0110)(这里为了简化说明支取一个字节即8位来运算) 位运算符比一般的算术运算符速度要快,而且可以实现一些算术运算符不能实现的功能.如果要开发高效率程序,位运算符是必不可少的.位运算符用来对二进制位进行操作,包括:按位与(&).按位或(|).按位异或(^).按位取反(~).按

java(第十五章)

第十五章 一.字符串类String 1.String是一个类,位于java.lang包中 2.创建一个字符串对象的2种方式: String 变量名="值"; String 对象名=new String("值"); 3.字符串的常用方法 3.1 字符串长度--length() 3.2 判断值是否相等 equals() 3.3 判断字符串对象地址是否相同 == 3.4 忽略 大小写 判断 equalsIgnoreCase() 3.5 大小写转换 toLowerCase(

java:第五章

第五章 循环结构 1.while循环 while(条件){ //代码块 } 解释:如果条件的结果为true,那么执行代码块, 当代码块执行完后,又会执行条件, 依次循环,直到结果为false,整个循环结束. 2.程序调试 第一步:设置断点(左键双击) 第二步:debug方式好执行代码 第三步:逐条执行代码调试(F6) 第四步:检查代码(看变量值的变化) 注意:在右上角可以切换java视图和debug视图. 3.do while循环 do{ //代码块 }while(条件) 解释:先执行代码块,然

别样JAVA学习(五)继承上

上章我们进行了面向对象的学习, 发现如果定义的几个类中的属性和方法重复, 代码是不是会显得很冗余啊?,有没有一种思想可以简化呢? 有!下面我们来看继承就能解决这个问题 1.继承-概述 继承: 1,提高了代码的复用性 2,让类与类之间产生了关系,也就是多态的特性 注意:千万不要为了获取其它类的功能,简化代码而继承 必须是类与类之间有所属关系才可以继承. Java语言中:java只支持单继承,不支持多继承 因为多继承容易带来安全隐患:当多个父类中定义了相同功能 且功能内容不同时,子类对象不确定运行哪

Java核心技术第四章----对象与类重难点总结

一.类之间的关系 类和类之间的关系,耦合度从高到低: is -a.继承.实现 has-a.组合.聚合.关联 user-a.依赖. 要求是:高内聚.低耦合. 继承("is-a") 继承(Inheritance),即"is-a"关系,是一种用于表示特殊与一般关系的.表示类与类(或者接口与接口)之间的父子关系.一般而言,如果类A扩展类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能.在JAVA中,用关键字extends表示继承关系 实现(Implementatio