java核心技术卷(一)-------继承

  • 一个对象变量可以指示多种实际类型的现象被称为多态
  • 在运行时能够自动地选择调用哪个方法的现象称为动态绑定
  • 如果是private方法、static方法、final方法或者构造器,那么编译器就可以准确地知道应该调用哪个方法,我们将这种调用方法称为静态绑定
类之间的关系

依赖—“uses-a”
聚合—“has-a”
继承—“is-a”

“is-a”规则的另一种表书法是置换原则

Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;

staff[0] 与 boss 引用同一个对象,但编译器将 staff[0]看成 Employee 对象
这意味着,可以这样调用

boss.setBonus(5000);//OK

但不能这样调用

staff[0].setBonus(5000);//Error

这是因为 staff[0] 声明的类型是 Employee ,而 setBonus 不是 Employee 类的方法

理解方法调用
父类
Employee e;
getSalary()
子类
Manager
覆盖
getSalary()

在运行时,调用 e.getSalary() 的解析过程为:

  1. 首先,虚拟机提取 e 的实际类型的方法表。既可能是 Employee、Manager 的方法表,也可能是 Employee 类的其他子类的方法表。

每次调用方法都要进行搜索,时间开销非常大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名(方法的名字和参数列表)和实际调用的方法。

  1. 虚拟机搜索定义 getSalary 签名的类。此时,虚拟机已经知道应该调用哪个方法
  2. 最后,虚拟机调用方法
阻止继承: final类和方法

不允许扩展的类称为 final
如果一个类声明为 final 类,只有其中的方法自动称为 final,而不包括域

Object:所有类的超类

在Java中,只有基本类型不是对象(不能用 new 的方式获取,但八种基本类型都有对应的包装类,包装类是对象),例如,数值、字符和布尔类型的值都不是对象(方法参数共有两种类型:基本数据类型和对象引用)

对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean----前 6 个类派生于公共的超类 Number

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能让对象参数引用一个新的对象
hashCode 方法

散列码(hashcode)是由对象导出的一个整型值。散列码是没有规律的,如果 x 和 y 是两个不同的对象,x.hashCode() 和 y.hashCode() 基本上不会相同。

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

String s = "OK";
StringBuilder sb = new StringBuilder(s);
String t = "OK";
StringBuilder st = new StringBuilder(t);
System.out.println(s.hashCode() + "--" + sb.hashCode());
System.out.println(t.hashCode() + "--" + st.hashCode());
2524--356573597
2524--1735600054

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

String 类使用下列算法计算散列码:

int hash = 0;
for(int i = 0; i < length(); i++){
	hash = 31 * hash + charAt(i);
}
toString 方法

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

[x=1,y=2]

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

Point p = new Point(10,20);
String message = "The current position is " + p;//自动调用 toString 方法

如果 x 是任意一个对象,并调用System.out.println(x);
println 方法就会直接调用 x.toString 方法,用来打印输出对象所属的类名和散列码。
调用System.out.println(System.out)将输出下列内容

java.io.PrintStream@f6684

这是因为 PrintStream 类的设计者没有覆盖 toString 方法

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

import java.lang.reflect.Array;
import java.util.Arrays;

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));
        int[] c = Arrays.copyOf(a, 3);
        System.out.println(c.getClass().getName());//[I
        for (int i = 0; i < c.length; i++) {
            System.out.print(c[i] + " ");
        }

        String[] b = {"Tom","Dick","Harry"};
        b = ((String[]) goodCopyOf(b, 10));
        System.out.println(Arrays.toString(b));
        System.out.println(b.getClass().getSuperclass());//class java.lang.Object

        System.out.println("The following call will generate an exception");
        b = (String[]) badCopyOf(b, 10);//Object[]对象数组不能转换成String[]类型


    }

    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();
        System.out.println(cl.getName());//[I   [Ljava.lang.String
        if (!cl.isArray()){
            return null;
        }
        int length = Array.getLength(a);//获取数组长度
        Class c = cl.getComponentType();//获得数组类型,如int
        Object newArray = Array.newInstance(c, newLength);//静态方法newInstance构造新数组
        System.arraycopy(a, 0, newArray,0,Math.min(length, newLength));
        return newArray;

    }
}

输出如下

[I
[1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
[I
1 2 3 
[Ljava.lang.String;
[Tom, Dick, Harry, null, null, null, null, null, null, null]
class java.lang.Object
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 Reflect.CopyOfTest.main(CopyOfTest.java:23)

String[] 的类名是[Ljava.lang.String ;而 String.class 类名是 java.lang.String

如果知道一个 class 的完整类名,可以通过静态方法 Class.forName() 获取

Class cls = Class.forName("java.lang.String");

注意一下 Class 实例比较和 instanceof 的差别

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类

boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

:一个从开始就是 Object[] 的数组永远不能转换成 Employee[] 数组

System.out.println("The following call will generate an exception");
b = (String[]) badCopyOf(b, 10);

Object[]对象数组不能转换成String[]类型,这句话会抛出ClassCastException

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 Reflect.CopyOfTest.main(CopyOfTest.java:23)

需要能够创建与原数组类型相同的新数组,为此使用 Array 类中的静态方法 newInstance 方法,它能够构造新数组

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

调用时必须提供两个参数,一个是数组的元素类型,一个是数组长度

使用 Class 类的 getComponentType 方法确定数组对应的类型
调用 Array.getLength(a) 获得数组长度

调用任意方法

Method 类中有一个 invoke 方法
invoke 方法签名是:

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

第一个参数是隐式参数(方法名前隐匿的对象实例,或称之为方法调用的目标,例如 "this.方法"中的 this),对于静态方法,第一个参数可以忽略,即可以将它设置为null

package Reflect;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Method square = MethodTest.class.getMethod("square", double.class);
        Method sqrt = Math.class.getMethod("sqrt", double.class);

        printTable(1, 10, 10, square);
        printTable(1, 10, 10, sqrt);

    }

    public static double square(double x) {
        return x * x;
    }

    public static void printTable(double from, double to, int n, Method f) {

        System.out.println(f);
        double dx = (to - from) / (n - 1);
        for (double x = from; x <= to; x += dx) {
            double y;
            try {
                y = (double) f.invoke(null, x);
                //如果返回类型是基本类型,invoke方法会返回其包装类型,必须完成类型转换;
                // 对于静态方法,第一个参数可以可设置为null
                System.out.printf("%10.4f | %10.4f%n", x, y);//不是println
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

输出如下

public static double Reflect.MethodTest.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
动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:

// Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}

当执行 Main.java 时,由于用到了 Main,因此,JVM首先会把 Main.class 加载到内存。然而,并不会加载 Person.class,除非程序执行到 create() 方法,JVM发现需要加载 Person 类时,才会首次加载 Person.class。如果没有执行create() 方法,那么 Person.class 根本就不会被加载。

这就是 JVM 动态加载 class 的特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值