Java基础:Java核心技术提示的易忽略点 Ch4

404 查看

开篇
Java是一门不那么简单也不那么复杂的语言,Java里面有很多问题和特性是容易被使用者忽视的,这些问题也许会难住新手,同时也许会是老手不小心跌入的无故之坑,只有精于对基础的提炼才能最大程度地解决类似的疑问。所以,在读Cay.Horstmann的《Java核心技术》的过程中,我记录下这些所谓的易忽略的问题,这些问题将会持续更新在我的这个Segment Fault的博客下,也算是激励自己重新挖掘这些基础问题的内涵。这个博客将以原书中的章节为分割,大概会是每章一篇,持续更新,每篇的内容也不会一次全部写完,视我个人对问题的理解和我的阅读进度而定。

另外,我们从本书第一卷第四章开始,因为前三章无外乎就是在讲述历史和环境配置,我认为和我们的主题不大相关。

Core Java Volume 1

chap4

java类和包的访问权限

public,private等这些访问控制标识对java的类和包来说都是可用的,而且在类中的属性和方法、包中的方法来说访问权限上的定义也是共通的:

  • public属性和方法:这些方法的访问权限是公开的,也就是说所有的类内部和外部对象都可以访问这些公开的属性和方法;

  • public类:这些类的访问权限是公开的,也就是说在包内的类和包外的类都可以访问这些公开的类;

  • private属性和方法:这些方法是类私有的,也就是说只能被类内部所访问;

  • private类:这些类的访问权限是私有的,也就是说只能被包内部所访问。

究竟什么是CLASSPATH

顾名思义,classpath指的是类路径,也就是jvm在搜寻class二进制文件时搜寻的目录,在这个目录中如果找到需要的class文件则去执行,我们可以通过执行前为java命令设置-classpath参数或者直接设置CLASSPATH系统环境变量的形式去定义这个类路径,jvm就会去这个指定的路径下搜寻需要的class文件。
这个例子说明这个问题:
我们可以在系统环境变量配置文件中这样配置java的CLASSPATH:

export CLASSPATH=".:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar"

这样的配置文件等同于在java命令中的 -classpath参数:

java -classpath .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar Test

这样配置CLASSPATH环境变量或使用-classpath参数之后,jvm在寻找程序中使用的不包含在当前类中的类时就会去这些路径下寻找,比如说程序中使用了不包含在当前类中的com.rocking.People类(People类通过importcom.rocking.*引入的),那么就会依照类路径中指示的路径去寻找:
压缩入$JAVA_HOME/lib/dt.jar中的com/rocking/People.class(jdk提供的系统级jar包)
压缩入$JAVA_HOME/lib/tools.jar的com/rocking/People.class
./com/rocking/People.class(当前路径.拼接上class的全路径名)

package com.rocking;

public class People{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

import com.rocking.People;

public class Test {
    public static void main(String[] args) {
        People people = new People();       
    }
}

最重要的一点是,我们上面的讨论都是在jvm寻找class文件的过程中,而非javac编译器寻找待编译的java source文件的过程中,在编译中,我们必须通过package定义当前类所在的包(或者不明确定义直接使用default package),需要导入外部类时import相应包,只有这样才能通过编译。类路径只是让jvm能够通过配置好的全路径名找到所需的外部类。

setter的不可靠性

理论上我们将域属性设置为private的目的是为了不让程序能够轻易地访问和修改到这个对象私有的属性,而只能通过该类定义的public的getter和setter方法去访问和修改。但是这并不是完全可靠的,程序仍然可以通过一些灵巧的办法在某些合适的情况下获取和更改这些私有的属性值。

setter的不可靠性,其实只要能够确定这个私有属性的引用,是完全可以直接做更改的,当然前提是获取到这个引用,比如通过getter拿到这个属性的引用。这是一个例子:


class Secret{
    private StringBuffer secret = new StringBuffer("A Secret");
    public StringBuffer getSecret() {
        return secret;
    }
    public void setSecret(StringBuffer secret) {
        this.secret = secret;
    }
}
public class TestClass {
    public static void main(String[] args) {
        Secret secret = new Secret();
        StringBuffer saidSecret = secret.getSecret();
        saidSecret.append(":broken");
        System.out.println(secret.getSecret());
    }
}

当然。这个例子其实是个特例,因为我们获取到的私有属性的引用是StringBuffer类型,是可修改内容的引用,但是如果是String类型的secret,那么我们就没办法这样绕过setter去修改了,因为String类型的引用是不允许修改原值的。

private的访问权限

我们都知道的是private属性是私有的,那么到底是对象私有的还是类私有的呢?答案是后者,我们可以在类定义的内部访问私有属性,即使这个属性是其他对象的,只要是同类的即可,虽然我们很少这么使用,但是这却深刻地说明java中的访问权限在类的范畴上。下面说明了这样一个问题:

class Secret {
    private String secret;
    public String getSecret() {
        return secret;
    }
    public void setSecret(String secret) {
        this.secret = secret;
    }
    public void readOtherSecret(Secret otherSecret) {
        System.out.println(otherSecret.secret);
    }
}
public class TestClass {
    public static void main(String[] args) {
        Secret secret1 = new Secret();
        secret1.setSecret("secret A");
        Secret secret2 = new Secret();
        secret2.setSecret("secret B");
        secret1.readOtherSecret(secret2);
        secret2.readOtherSecret(secret1);
    }
}

static究竟是什么意思

static的本意是静态的,在java里面这个静态的意思是指存在于静态的类中的,而非程序开跑之后运行时的动态对象的,所以static的域属性也好,方法也好,都是指那些存在于类中的域属性和方法,虽然该类对应的对象中也存在static域属性和方法的拷贝,但是属于类的,对于它们的影响将会影响到整个类,换言之这些影响将会产生在所有的类对象中。
static的方法只能访问static的域属性,这个是怎么来的呢?其实就是因为static属性是属于整个类的,所以static的方法就不应该影响特定的对象的属性,在实现上说这个方法的隐式参数是不含有非static的方法的指向当前对象的this引用的,因此不能在static方法中访问非static属性。

java的方法参数传递

在C++中被反复向初学者讨论的一个问题就是swap函数的效果,如果参数是值传递的,则最终传入的两个值是不会被交换的,如果是指针(引用)传递的,那么最终传入的两个引用是会被交换的,那么java中的方法参数传递是哪种情况呢?答案是前者,即值传递,又叫做拷贝实参传递,即运行在方法中的形式参数是传递给方法的真实参数的拷贝,而非直接传入实际参数的指针。
下面的例子说明了这个问题:

class Some{
    private String someThing;
    public String getSomeThing() {
        return someThing;
    }
    public void setSomeThing(String someThing) {
        this.someThing = someThing;
    }
    public Some(String someThing) {
        super();
        this.someThing = someThing;
    }
}
public class TestClass {
    public static void swap(Some some1,Some some2){
        Some temp = some1;
        some2 = some1;
        some1 = temp;
    }
    public static void main(String[] args) {
        Some some1 = new Some("1");
        Some some2 = new Some("2");
        swap(some1, some2);
        System.out.println(some1.getSomeThing());
        System.out.println(some2.getSomeThing());
    }
}

类和对象初始化

我们都知道的是对象的初始化会引发类的初始化,初始化实现的方法也很多,最一般的初始化方法用于对象创建时对域属性的初始化,稍微少见的类的静态代码块用于对static静态域属性进行初始化,而初始化代码块则用于对象的域属性(包括static域属性)进行初始化,那么一般而言,初始化的顺序是怎样的呢?
首先执行静态代码块初始化static类静态域属性用于在整个类及其对象共享;
其次执行初始化代码块初始化域属性以完成对对象的个性化的域属性的初始化;
最后执行对象的初始化方法以完成进一步的域属性的初始化。

class Some{
    private String someThing;
    private static String staticSomeThing;
    static
    {
        staticSomeThing = "static";
        System.out.println("static block triggered");
    }
    {
        someThing = "some";
        System.out.println("init block triggered");
    }
    public String getSomeThing() {
        return someThing;
    }
    public void setSomeThing(String someThing) {
        this.someThing = someThing;
    }
    public Some(String someThing) {
        this.someThing = someThing;
        System.out.println("init method triggered");
    }
}
public class TestClass {
    public static void main(String[] args) {
        Some some1 = new Some("1");
    }
}

未完成。。。