字符串

字符串是存储在字符串常量池中的。例如以下的两个字符串的内存地址值是一样的:

String str1 = "hello" + "world";
String str2 = "helloworld";
System.out.println(str1 == str2);      // true
System.out.println(str1.equals(str2)); // true

String str3 = "hello";
String str4 = "world";
String str5 = str3 + str4;
System.out.println(str5 == str2);      // false
System.out.println(str5.equals(str2)); // true

在以上的代码中str2和str5的地址值不相同,如果我们对str5使用intern()方法即:

String str6 = str5.intern();           // native方法
System.out.println(str6 == str2);      // true

就可以返回true。

字符串的intern方法

面试题:假设字符串常量池中不存在字符串"hello",那么String s = new String("hello")创建了几个对象?

:两个堆空间的value值(字符数组)和字符串常量池中的hello实体。我们可以通过查看new String(String str)的源码:

 /** The value is used for character storage. */
 private final char value[];

 /**
 * Initializes a newly created {@code String} object so that it represents
 * the same sequence of characters as the argument; in other words, the
 * newly created string is a copy of the argument string. Unless an
 * explicit copy of {@code original} is needed, use of this constructor is
 * unnecessary since Strings are immutable.
 *
 * @param  original
 *         A {@code String}
 */
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

用句柄操作对象

java中一切皆对象,但是我们操作的实际上是指向这个对象的句柄(Handle),这个句柄也叫做引用(Reference)或者指针(Pointer)。我们可以将这一情形想象成用遥控器(Handler)操作电视机(Object)。没有电视机,遥控器。

即使没有电视机,遥控器也可以单独存在。即句柄是可以单独存在的(并不指向任何实体)。例如:

String s;

以上的java代码创建的仅仅是句柄而不是对象。此时如果向s发送一条消息,将会或者一个运行时异常,因此更安全的做法是:创建一个句柄的时候进行显式初始化:

String s = "";

java程序运行时数据的存放位置

  • Register。处理器内部(最快),由于数量有限,所以寄存器是根据需要有编译器分配的。我们对它没有直接的控制权,也不可能在程序中找到寄存器的任何踪迹。

  • Stack。驻留与常规RAM区域,速度仅次于寄存器。我们可以通过它的“堆栈指针”获得直接的处理支持。(指针下移创建新的内存;指针上移释放内存)。创建程序时java编译器必须准确知道堆栈内保存的所有数据的“长度”以及“存在的时间”(必须生成相应的代码,以便于向上或者向下移动指针)。这一限制无疑影响了程序的灵活性,所以java将对象的句柄保存在堆栈中,但是对象并不存放在其中。

  • Heap。一种常规用途的内存池(也是在RAM区域),保存java对象。Heap最吸引人的地方在于:编译器并不必要知道要从堆中分配多少存储空间,也不必要知道存储的数据要在堆中停留多长时间——用堆保存数据会得到更大的灵活性。然而every coin have two slices,在堆中分配存储空间会花费更长的时间!

  • 静态存储。“静态”(Static)指的是“位于固定位置”。程序运行期间,静态存储的数据将随时等候调用。我们可以用static关键字指出一个对象的特定元素是静态的,但是java对象本身永远不会置入静态存储空间

  • 常数存储。常数存储通常直接置于一个程序代码内部。这样做是安全的,因为他们永远不会被改变。有的常数需要严格保护,可以考虑将他们置入只读存储器(ROM)。

  • 非RAM存储:将数据完全保存子啊一个程序之外。对典型的2个例子就是“流式对象”和“固定对象”。

    • 流式对象:对象-->字节流-->另一台机器

    • 固定对象:对象-->磁盘

基本数据类型

基本数据类型由于比较常用,而堆栈的效率又高于堆。所以基本数据类型都是保存在堆栈中。对于基本数据类型我们不需要用new,而是创建了一个并非句柄的“自动变量”,该变量容纳了具体的值,并保存在堆中可以更高效的存取。

java中的数组

在C、C++中使用数组是非常危险的,因为那些数组只是内存块,如果程序访问自己内存块之外的数据或者在初始化之前使用内存会产生不可预料的后果。在C++中应该尽量避免使用数组而换用Standard TemplateLibrary中更安全的容器。java中的数组会自动记性范围检查会造成少量的内存开销。但是我们可以换来更高的工作效率。

变量作用域

变量的作用域是由花括号的位置决定的。
在C、C++中以下的代码是合法的:

{
    int x = 10;
    {
        int x = 100; // 不合法Duplicate local variable x
    }
}

但是在java中编译器会认为变量x已经被定义,所以C、C++能将一个变量“隐藏”在一个更大的作用域中,java的设计者认为这样使程序产生了混淆。

注意区分成员变量和局部变量

成员变量都有默认值,而局部变量必须进行初始化。

文档注释

文档注释只能为public和protected的成员处理文档,private和default的不会被javadoc提取。文档注释中可以嵌入html,例如:

javadoc

异常

方法抛出异常的时候,该方法会从栈上立即被取出,而异常再度丢给栈上的方法(也就是调用方),如果调用方没有对异常进行处理而是继续抛出异常,调用方就会从栈上弹出,异常再度交给此时的栈顶方法,如此一路下去……
finally中的代码有一种情况下是执行不到的:finally的前面出现了System.exit(0)。

static和final

static只能修饰类的成员(变量和方法),不能修饰局部变量。static变量存放在方法区。随着类的加载而加载,存在方法区中,随着类的消失而消失,生命周期最长。如果没有给定初值,static变量会被默认置0.(或者null)。

Q:如果一个类被标记为final,再将该类中的方法标记位final是不是很多余?
A:不只是多余,而且是多了很多!如果一个类为final,那么它根本就没有子类,根本不可能覆写父类中的方法(只有继承才有覆写)。

使用类加载器加载配置文件

工程目录如下:

使用类加载器加载配置文件

加载该配置文件应该这样写:

public class Test {

    @org.junit.Test
    public void test() throws IOException {
        
        ClassLoader classLoader = this.getClass().getClassLoader();
        InputStream is = classLoader.getResourceAsStream("org/gpf/conf/db.properties");
        Properties properties = new Properties();
        properties.load(is);
        properties.list(System.out);
    }
}

利用反射获取父类的泛型

public class Person <K,V>{
    // some code...
}

public class Student extends Person<String,Integer> {
    // some code...
}

Class<?> clazz =  Student.class;
Type type = clazz.getGenericSuperclass(); // org.gpf.Person<java.lang.String, java.lang.Integer>
ParameterizedType parameterizedType = (ParameterizedType) type; // type的子接口
for (Type c : parameterizedType.getActualTypeArguments()) {
    System.out.println(((Class<?>)c).getName()); // Class是Type接口的实现类
}

java中的枚举

所谓枚举,就是枚举类的对象的个数是有限的,可以穷举出来的类。实际上单例模式也可以使用枚举来实现(Effective Java中的单例经典实现,枚举只有一个成员)。jdk1.5之前需要自定义枚举类,jdk1.5之后提供了enum关键字用于定义枚举类。

例如季节是有限的:春夏秋冬。

// jdk1.5之前的枚举类
public class Season {

    // 1.声明final属性
    
    private final String seasonName;     // 季节名
    private final String seasonDescribe; // 季节描述
    
    //  2.为保证实例的数目是确定的需要私有化构造器,在构造器中初始化final的属性
    private Season(String seasonName,String seasonDescribe) {
        this.seasonName = seasonName;
        this.seasonDescribe = seasonDescribe;
    }

    // 3.通过公用的方法调用属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDescribe() {
        return seasonDescribe;
    }
    
    // 4.内部实例化枚举类的对象
    public static final Season SPRING = new Season("spring", " 春暖花开");
    public static final Season SUMMER = new Season("summer", " 夏日炎炎");
    public static final Season FALL = new Season("fall", " 秋高气爽");
    public static final Season WINTER = new Season("spring", " 白雪皑皑");

    public String show() {
        return "Season [seasonName=" + seasonName + ", seasonDescribe="
                + seasonDescribe + "]";
    }
    
}

我们可以使用以下的方式进行调用

Season season = Season.FALL;
System.out.println(season.show());
System.out.println(season.getSeasonName() + "-->" + season.getSeasonDescribe());

jdk1.5之后我们可以使用enum关键字来简化枚举类的定义:

// jdk1.5之后的枚举类
public enum Season {

    SPRING("spring", " 春暖花开"),
    SUMMER("summer", " 夏日炎炎"),
    FALL("fall", " 秋高气爽"),
    WINTER("spring", " 白雪皑皑");
    
    private final String seasonName;     // 季节名
    private final String seasonDescribe; // 季节描述
    
    private Season(String seasonName,String seasonDescribe) {
        this.seasonName = seasonName;
        this.seasonDescribe = seasonDescribe;
    }

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDescribe() {
        return seasonDescribe;
    }
    
    public String show() {
        return "Season [seasonName=" + seasonName + ", seasonDescribe="
                + seasonDescribe + "]";
    }
    
}

这样使用枚举类:

Season season = Season.FALL;
System.out.println(season.show());
System.out.println(season.getSeasonName() + "-->" + season.getSeasonDescribe());

Season[] seasons = Season.values(); // 返回所有枚举类的对象的数组
for (Season s : seasons) {
    System.out.println(s.getSeasonName());
}

season = Season.valueOf("SUMMER"); // 返回枚举类型的对象
System.out.println(season);

我们也可以让枚举类型实现接口:

// jdk1.5之后的枚举类
public enum Season implements Info{

    SPRING("spring", " 春暖花开"){
        @Override
        public void show() {
            System.out.println(1);
        }
    },
    SUMMER("summer", " 夏日炎炎"){
        @Override
        public void show() {
            System.out.println(2);
        }
    },
    FALL("fall", " 秋高气爽"){
        @Override
        public void show() {
            System.out.println(3);
        }
    },
    WINTER("spring", " 白雪皑皑"){
        @Override
        public void show() {
            System.out.println(4);
        }
    };
    
    private final String seasonName;     // 季节名
    private final String seasonDescribe; // 季节描述
    
    private Season(String seasonName,String seasonDescribe) {
        this.seasonName = seasonName;
        this.seasonDescribe = seasonDescribe;
    }

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDescribe() {
        return seasonDescribe;
    }
    
    @Override
    public void show() {
        System.out.println("Season [seasonName=" + seasonName + ", seasonDescribe="
                + seasonDescribe + "]");
    }
    
}

以上的程序中每个枚举类型的实例都各自实现自己的方法!


雨碎江南
1.8k 声望32 粉丝

风风雨雨寒寒暖暖处处寻寻觅觅,