美文网首页
Java基础(不定期更新)

Java基础(不定期更新)

作者: MOVE1925 | 来源:发表于2017-04-03 16:23 被阅读32次

    方法调用

    下面假设要调用 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)。反射机制可以用来:

    • 在运行时分析类的能力
    • 在运行时查看对象,例如,编写一个 toString 方法供所有类使用
    • 实现通用的数组操作代码
    • 利用 Method 对象

    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 都会关闭

    相关文章

      网友评论

          本文标题:Java基础(不定期更新)

          本文链接:https://www.haomeiwen.com/subject/gqxyottx.html