《java核心技术卷一》学习笔记--第5章 继承

继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。

一、  类、超类和子类

Super关键字

子类构造器可以通过super实现对超类构造器的调用。使用super调用构造器对这部分的语句必须是子类构造器的第一条语句。

This关键字用途:一是引用隐式参数,二是调用该类其他的构造器。Super关键字的用途:一是调用超类的方法,二是调用超类的构造器。

方法调用的过程:

1)编译器查看对象的声明类型和方法名。此时,编译器已获得所有可能被调用的候选方法。

2)编译器将查看调用方法时提供的参数类型。此时,编译器已获得需要调用的方法名字和参数类型。

3)如果是private方法、static方法、final方法,那么编译器将可以准确的知道应该调用哪个方法,我们将这种调用方式称为静态绑定。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。

4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。

final类和方法

不允许扩展的类被称为final类。类中的特点方法也可以被声明为final(final类中的所有方法自动地称为final方法)。

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

变量

变量实质是一小块内存单元,这一小块内存里存储着变量的值,当变量指向一个对象时,这个变量就被称为变量引用。

A a = new A();

a为变量引用,引用了一个A对象。变量a 的值为它所引用对象的地址。

强制类型转化

将一个子类的引用赋给一个超类变量,编译器是允许的,但将一个超类的引用赋给一个子类变量,必须进行类型转换。在将超类转换为子类之前,应该使用instanceOf进行检查。

抽象类

如果自上而下在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类。

现在有Employee和Student两个类,继承于Person类。Person类中有一个getName方法。

现在我们增加一个getDescription方法,它可以返回对一个人的简短描述。

但是在Person类中,我们只能得到姓名,但不能得到具体的描述。

这时候我们就需要使用abstract关键字。

abstractclass Person{

    publicabstract String getDescription();

    //不用实现具体方法

}

注意:包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法外,抽象类还可以包含具体数据和具体方法。

如,Person类中还保存姓名和一个返回姓名的方法。

abstractclass Person{

    private String name;

    publicvoid setName(String name) {

         this.name = name;

    }

    public String getName() {

         returnname;

    }

    publicabstract String getDescription();

    //不用实现具体方法,具体实现在子类中

}

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

注意:抽象类不能被实例化。也就是说如果将一个类声明为abstract,就不能创建这个类的对象。

class Student extends Person{

     private String major;

     public Student(String name,String major) {

          super(name);

          this.major = major;

     }

     @Override

     public String getDescription() {

          return"a student major in " + major;

     }

}

在Student类中定义了getDescription方法。因此,在Student类中的全部方法都是非抽象的,这个类不再是抽象类。

Protected

我们都知道,最好将类中的域(field)标记为private,而方法标记为public。

有时候,人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。

为此,需要将这些方法或域声明为protected。子类可以访问超类中声明为protected的域但不能访问其他声明为private的域。这种限制有助于避免滥用受保护机制,使得子类只能获得访问受保护域的权利。

如果需要限制某个方法的使用,就可以将它声明为protected。这表明子类得到信任,可以正确地使用这个方法,而其他类不行。

总结java用于控制可见性的4个访问修饰符:

1.  private:仅对本类可见。

2.  public:对所有类可见。

3.  protected:对本包和所有子类可见。

4.  默认(缺省、没有修饰符):对本包可见。

二、  Object:所有类的超类

Object类是java中所有类的超类,在java中每个类都是由它扩展来的。

在java中,只有基本类型不是对象,例如,数值、字符和布尔类型的值都不是对象。

所有的数组类型,不管是对象数组还是基本类型数组都扩展了Object类。

equals方法

Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用(地址)。

getClass方法将返回一个对象所属的类。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

hashCode方法

散列码(hashCode)是由对象导出的一个整形值。hashCode没有规律。其值为对象的存储地址。

toString方法

返回对象值的字符串。

getClass()。getName()获得类名的字符串。

Object类定义了toString方法,用来打印输出对象所属的类名+hashCode

tips:打印数组时:

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

String s = Arrays.toString(arr);

打印二维数组:

Arrays.deepToString(arr);


三、  泛型数组列表

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

Tips:

ArrayList类与Vector类区别:

1.  Vector属于线程安全级别,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销

2.  ArrayList在内容不够时默认是扩展50%+1个,Vector是默认扩展1倍

3.  Vector提供indexOf(obj,start)接口,ArrayList没有。

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

所有基本类型都有一个与之对应的了类。通常,这些类称为包装器。

Integer、Long、Float、Double、Short、Byte、Character、Boolean(前六个类派生与公共的超类Number)。

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

自动拆箱、自动装箱

注意:装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。

五、  参数数量可变的方法

我们来看一下System.out.printf()方法的定义。

public PrintStream printf(String format, Object ... args) {

        return format(format, args);

    }

这里的省略号...是java代码的一部分,它表明这个方法可以接收任意数量的对象(除fmt参数之外)。

实际上,printf方法接收两个参数,一个是格式字符串,另一个是Object[]数组,其中保存着所有的参数。现在扫描fmt字符串,并将第i个格式说明符与args[i]的值匹配起来。

现在我们自己定义一个可变参数的方法。

    publicstaticdouble max(double... values){

         doublelargest = Double.NEGATIVE_INFINITY;

         for (doublev : values) {

             if(v > largest)

                 largest = v;

         }

         returnlargest;

    }

六、  枚举类

所有的枚举类都是Enum类的子类。

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

publicclass EnumTest {

    publicstaticvoid main(String[] args) {

         Size[] size = Size.values();

         System.out.println(Arrays.toString(size));

    }

}

 

enum Size{

    small,

    medium,

    large;

}

结果

[small, medium, large]

七、  反射

能够分析类能力的程序称为反射

反射是指在程序运行期间发现更多的类及其属性的能力。

Class类

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

获得Class对象的三种方法

1.getClass()

String s = "abc";

Classc1 = s.getClass();

2.forName

String className = "java.lang.String";

Classc2 = Class.forName(className);

这个方法只有在className是类名或接口名才能够执行。否则,forName方法将抛出一个checkedException。

3.T.class

Classc3 = String.class;

System.out.println(c3.getName());

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

newInstance()方法可以用来动态地创建一个类的实例。

e.getClass().newInstance();

创建了一个与e具有相同类类型的实例。newInstance方法调用默认的构造器。

String s = “java.lang.String”;

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

Tips:

new关键字与newInstance()方法的区别:

1)类的加载方式不同

在执行Class.forName(“ ”)时,JVM会在classpath中去找对应的类并加载,这时JVM会执行该类的静态代码段。在使用newInstance()方法的时候,必须保证这个类已经加载并已经连接了,而这可以通过Class的静态方法forName()来完成。

2)所调用的构造方法不尽相同

new关键字能调用任何构造方法。

newInstance()只能调用无参构造方法。

3)执行效率不同

new关键字是强类型的,效率相对较高。

newInstance()是弱类型的,效率相对较低。

捕获异常

抛出异常比终止程序要灵活得多,这是因为可以提供一个“捕获”异常的处理器(handler)对异常情况进行处理。

下面是一个实现最简单的处理器

try {

         String name = ...;//get class name

         Class c1 = Class.forName(name);//might throw exception

         do something with c1

    } catch (Exception e) {

         e.printStackTrace();

    }

利用反射分析类的能力

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

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

Field类有一个getType方法,用来返回描述域所属类型的Class对象。

Method个Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。

这三个类还有一个叫做getModifiers的方法,它将返回一个整形数值,用不同的位开关描述public和static这样的修饰符使用状况。另外,可以使用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整数数值。还可以利用Modifier.toString方法将修饰符打印出来。

Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。

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

下面这段代码显示了如何打印一个类的全部信息的方法。

import java.util.*;

import java.lang.reflect.*;

 

publicclass ReflectionTest {

    publicstaticvoid main(String[] args) {

         Scanner in = new Scanner(System.in);

         System.out.println("Enter class name:");

         String name = in.next();

   

         try{

             Class c1 = Class.forName(name);

             Class superc1 = c1.getSuperclass();

             String modifiers = Modifier.toString(c1.getModifiers());

             if(modifiers.length() > 0)

                  System.out.print(modifiers+" ");

             System.out.print("class "+name);;

             if(superc1 != null && superc1 != Object.class)

                  System.out.print(" extends "+superc1.getName());

             System.out.print("\n{\n");

             printConstructors(c1);

             System.out.println();

             printMethods(c1);

             System.out.println();

             printFields(c1);

             System.out.println("}");

         }catch (ClassNotFoundException e) {

             e.printStackTrace();

         }

         System.exit(0);

    }

 

    /*

     * 打印类中的所有构造方法

     */

    privatestaticvoid printConstructors(Class c1) {

         Constructor[] constructors = c1.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 + "(");

            

             //打印参数类型

             Class[] paramTypes = c.getParameterTypes();

             for (intj = 0; j < paramTypes.length; j++) {

                  if(j > 0)

                      System.out.print(",");

                  System.out.print(paramTypes[j].getName());

             }

             System.out.println(");");

         }

    }

   

    /*

     * 打印类中的所有方法

     */

    privatestaticvoid printMethods(Class c1) {

         Method[] methods = c1.getDeclaredMethods();

        

         for (Method m : methods) {

             Class retType = m.getReturnType();

             String name = m.getName();

            

             System.out.print("  ");

             //打印修饰符,返回类型,方法名

             String modifiers = Modifier.toString(m.getModifiers());

             if(modifiers.length() > 0)

                  System.out.print(modifiers+" ");

             System.out.print(retType.getName()+" "+name+"(");

             Class[] paramTypes = m.getParameterTypes();

             for (intj = 0; j < paramTypes.length; j++) {

                  if(j > 0)

                      System.out.print(", ");

                  System.out.print(paramTypes[j].getName());

             }

             System.out.println(");");

         }

    }

    /*

     * 打印类中的所有字段

     */

    privatestaticvoid printFields(Class c1) {

         Field[] fields = c1.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+";");

         }

        

    }

}

运行结果:

Enter class name:

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;

}

八、  继承的设计技巧

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

2.不要使用受保护的域

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

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

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

6.不要过多地使用反射

反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通用性的程序。这种功能对于编写系统程序来说极其实用,但是通常不适于编写应用程序。反射是很脆弱的,即编译器很难帮助人们发现程序中的错误,因此只有在运行时才会发现错误并导致异常。

猜你喜欢

转载自blog.csdn.net/Ontheroad_/article/details/80558525