Java基础(不定期更新)

2017-04-03  本文已影响32人  MOVE1925

方法调用

下面假设要调用 x.f(args),隐式参数 x 声明为类 C 的一个对象,下面是调用过程的详细描述:
1、编译器查看对象的声明类型和方法名。假设调用 x.f(param),且隐式参数 x 声明为类 C 的对象。需要注意的是:有可能存在多个名字为 f,但参数类型不一样的方法。例如,可能存在方法 f(int) 和方法 f(String)。编译器将会一一列举所有 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法(超类的私有方法不可访问)。
至此,编译器已获得所有可能被调用的候选方法。

2、接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程就被称为重载解析(overloading resolution)。例如,对于调用 x.f("Hellp") 来说,编译器将会挑选 f(String),而不是 f(int)。由于允许类型转换(int 可以转换成 double),所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
至此,编译器已获得需要调用的方法名字和参数类型。

方法的名字和参数列表称为方法的签名。例如,f(int) 和 f(String)是两个具有相同名字,不同签名的方法。如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。
不过,返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。例如,假设 Employee 类有
public Employee getBuddy(){...}
经理不会想找这种地位低下的员工,为了反映这一点,在后面的子类 Manager 中,可以按照如下所示的方式覆盖这个方法
public Manager getBuddy(){...}//OK to change return type
我们说,这两个 getBuddy 方法具有可协变的返回类型

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

4、当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设 x 的实际类型是 D,它是 C 类的子类。如果 D 类定义了方法 f(String),就直接调用它;否则,将在 D 类的超类中寻找 f(String),一次类推。

强制类型转换

1、在进行类型转换之前,应先使用 instanceof 操作符检查是否能够成功地转换

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

散列码

散列码(hash code)是由对象导出的一个整型值。由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。

来看一个小栗子:

String s = "OK";
StringBuilder sb = new StringBuilder(s);
System.out.println("s hash code is : " + s.hashCode());
System.out.println("sb hash code is : " + sb.hashCode());

String t = new String("OK");
StringBuilder tb = new StringBuilder(t);
System.out.println("t hash code is : " + t.hashCode());
System.out.println("tb hash code is : " + tb.hashCode());
散列值

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

如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中。

hashCode 方法应该返回一个整形数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。

参数数量可变的方法

在 Java SE 5.0 以前的版本中,每个 Java 方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法。

例如经常使用的 printf:

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

在上面的两条语句中,尽管一个调用包含两个参数,另一个调用包含三个参数,但它们调用的都是同一个方法。

printf 方法是这样定义的:

public class PrintStream
{
    public PrintStream printf(String fmt, Object... args){
        return format(fmt, args);
    }
}

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

实际上,printf 方法接收两个参数,一个是格式字符串,另一个是 Object[] 数组,其中保存着所有参数(如果调用者提供的是整型数组或者其他基本类型的值,自动装箱功能将把它们转换成对象)。现在将扫描 fmt 字符串,并将第 i 个格式说明符与 args[i] 的值匹配起来。

换句话说,对于 printf 的实现者来说,Object... 参数类型与 Object[] 完全一样。

编译器需要对 printf 的每次调用进行转换,以便将参数绑定到数组上,并在必要的时候进行自动装箱:

System.out.printf("%d %s", new Object[] {new Integer(n), "widgets"});

自定义可变参数的方法,下面是一个简单的示例:其功能为计算若干个数值的最大值

public static double max(double... values){
    double largest = Double.NEGATIVE_INFINITY;
    for (double v : values) if ( v > largest) largest = v;
    return largest;
}

可以像下面这样调用 max 方法:

double m = max(3.1, 40, -5);

编译器将new double[] {3.1, 40, -5}传递给 max 方法。

反射

能够分析类能力的程序称为反射(reflective)。反射机制可以用来:

Class 类

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

然而,可以通过专门的 Java 类访问这些信息。保存这些信息的类被称为 Class

Object 类中的 getClass() 方法将会返回一个Class类型的实例:

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

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

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

如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在 className 是类名或接口名时才能够执行。否则,forName 方法将抛出一个 checked exception(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler)

异常

Java 异常结构图

finally 子句

当 finally 子句包含 return 语句。假设利用 return 语句从 try 语句块中退出,在方法返回之前,finally 子句的内容将被执行。如果 finally 子句中也有一个 return 语句, 这个返回值将会覆盖原始的返回值

public static int f(int n) {
    try {
        int r = n * n;
        return n;
    } finally {
        if (n == 2) {
            return 0;
        }
    }
}

如果调用 f(2),那么 try 语句块的计算结果为 r = 4,并执行 return 语句。然而,在方法真正返回之前,还要执行 finally 子句。finally 子句将使得方法返回 0,这个返回值覆盖了原始的返回值 4。

所以在使用 finally 的时候要稍微注意些,否则会返回意想不到的结果,下面还有个栗子

清理资源的方法也有可能抛出异常。假设希望能够确保在流处理代码中遇到异常时将流关闭

InputStream in = ...;
try {
    code that might throw exceptions
} finally {
    in.close();
}

假设在 try 语句块中的代码抛出了一些非 IOException 的异常,这些异常只有这个方法的调用者才能够给予处理。执行 finally 语句块, 并调用 close 方法,而 close 方法本身也有可能抛出 IOException 异常。当出现这种情况时,原始的异常将会丢失,转而抛出 close 方法的异常

如何解决 finally 子句覆盖原始异常的情况呢?很容易想到的是在 finally 子句中对 close 方法再进行try/catch,但是这样代码就比较繁琐

InputStream in = ...;
Exception ex = null;
try {
    try {
        code that might throw exceptions
    } catch (Exception e) {
        ex = e;
        throw e;
    }
} finally {
    try {
        in.close();
    } catch (Exception e) {
        if (ex == null) {
            throw e;
        }
    }
}

这种复杂的代码不是开发者想看到的,所以在Java SE 7 中有一个带资源的 try 语句,用来解决资源关闭问题就容易的多

带资源的 try 语句(try-with-resources)的最简形式为:

try (Resource res = ...) {
    work with res
}

try 语句块退出时,会自动调用 res.close()。例如,要读取一个文件中的所有单词:

try (Scanner in = new Scanner(new FileInputStream("/home/words")),
    "UTF-8") {
    while (in.hasNext()) {
        System.out.println(in.next());
    }
}

这个语句块正常退出时,或者存在一个异常时,都会调用 in.close() 方法,就好像使用了 finally 语句块一样

还可以指定多个资源, 例如:

try (Scanner in = new Scanner(new FileInputStream("/home/words"), "UTF-8");
    PrintWriter out = new PrintWriter("out.txt")) {
    while (in.hasNext()) {
        System.out.println(in.next().toUpperCase());
    }
}

不论这个语句块如何退出,in 和 out 都会关闭

上一篇 下一篇

猜你喜欢

热点阅读