Java学习笔记(三)


学习内容来自于mooc中华东师范大学课程Java核心技术。

常量设计和常量池:

常量设计

常量:不会修改的变量
但是Java没有关键字constant
按照之前关键词对变量的修饰:

  • 不能修改,final
  • 不会修改/只读/只要一份,static
  • 方便访问,public

结合一下,Java常量的关键词即为 public static finalpublic final static
这样就保证了定义的变量只有一份且不会被修改。
设计常量时,变量名字建议全大写,并用连字符( _ )相连。如UPPER_BOUND。

public class Constants {
    public final static double PI_NUMBER = 3.1415926;
    public static final String DEFAULT_COUNTRY = "China";

    public static void main(String[] args) {
        System.out.println(Constants.PI_NUMBER); // 也属于静态变量
        System.out.println(Constants.DEFAULT_COUNTRY);
    }
}

还有一种特殊情况,接口中定义的变量默认都是常量。
哪怕没有用public static final修饰,也会默认认为如此。
而且如果给接口中定义的变量加关键字private,会出现编译无法通过的错误。
并且如果实现了接口的类尝试修改常量,也会报错。
The final field Animal.color cannot be assigned

public interface Animal {
    String color = "blue";
    public void eat();
    public void move();
    
}

public class Cat implements Animal {
    
    public void eat() {
        System.out.println("Cat can eat!");
    }
    public void move() {
        System.out.println("Cat can move!");
    }

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.color = "White"; // error,The final field Animal.color cannot be assigned
    }
}

常量池

先看一个例子:

public class IntegerTest {
    
    public static void main(String[] args) {
        Integer n1 = 127;
        Integer n2 = 127;
        System.out.println(n1 == n2);
        Integer n3 = 128;
        Integer n4 = 128;
        System.out.println(n3 == n4);
        Integer n5 = new Integer(127);
        System.out.println(n1 == n5);
    }
}

输出结果:

true
false
false

这是为什么呢?
Java为很多基本类型的包装类都建立了常量池。
常量池:相同的值只存储一份,节省内存,共享访问。
基本类型的包装类与其对应的常量池为:

基本类型包装类常量池
boolBooleantrue/false
byteByte-128-127
shortShort-128-127
intInteger-128-127
longLong-128-127
charCharacter0-127
floatFloat
doubleDouble

所以,刚刚的例子中n1和n2指向了常量池中的同一个对象。而n3和n4申请的值不在常量池中,也就是各自有一个Integer类的对象。同理,n5是new了一个对象,不适用常量池中中的对象。

public class CacheTest {
    
    public static void main(String[] args) {
        Boolean b1 = true;
        Boolean b2 = true;
        System.out.println("Boolean Test: " + String.valueOf(b1 == b2));

        Byte b3 = 127;
        Byte b4 = 127;
        System.out.println("Byte Test: " + String.valueOf(b3 == b4));

        Character c1 = 127;
        Character c2 = 127;
        System.out.println("Character Test: " + String.valueOf(c1 == c2));

        Short s1 = -128;
        Short s2 = -128;
        System.out.println("Short Test: " + String.valueOf(s1 == s2));

        Long l1 = -128L;
        Long l2 = -128L;
        System.out.println("Long Test: " + String.valueOf(l1 == l2));

        Float f1 = 0.5f;
        Float f2 = 0.5f;
        System.out.println("Float Test: " + String.valueOf(f1 == f2));
    }
}

输出结果:

Boolean Test: true
Byte Test: true
Character Test: true
Short Test: true
Long Test: true
Float Test: false

字符串常量都建立了常量池缓存机制

		String s1 = "abc";
        String s2 = "abc";
        String s3 = "ab" + "c"; // 都是常量,编译器会优化
        String s4 = "a" + "b" + "c";
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s1 == s4);

输出结果都为true。

常量池的作用很明显,就是为了节约内存。

基本类型的包装类和字符串有两种创建方式:

  • 常量式创建,放在栈内存,将被常量化。如 String s1 = “abc”;
  • new对象进行创建,放在堆内存,不会常量化。如 Intege n = new Integer(10);

这两种创建方式创建的对象存放的位置不同。
栈内存容量小但是速度快,堆内存容量大但是速度慢。
看下面两个例子。

public static void main(String[] args) {
        int i1 = 10;
        Integer i2 = 10;    // 10装入常量池,i2指向它
        System.out.println(i1 == i2); // true
        // 基本类型和包装类进行比较,包装类自动拆箱变为基本类型

        Integer i3 = new Integer(10);
        System.out.println(i1 == i3); // true
        // 基本类型和包装类进行比较,包装类自动拆箱变为基本类型

        System.out.println(i2 == i3); // false
        // 对象比较,比较其地址
        // i2在常量池,存在栈内存中;i3是new出的对象,存在堆内存中


        Integer i4 = new Integer(5);
        Integer i5 = new Integer(5);
        System.out.println(i1 == (i4+i5)); // true
        System.out.println(i2 == (i4+i5)); // true
        System.out.println(i3 == (i4+i5)); // true
        // + 操作将会使i4+i5自动拆箱为基本类型并得出结果10
        // 基本类型10和对象比较,会使对象自动拆箱为基本类型

        System.out.println(i4 == i5);  // false
        // 堆内存中的两个不同对象

        Integer i6 = i4 + i5;
        // + 操作会使i4+i5自动拆箱为基本类型并得出结果10,之后该对象指向常量池中的10
        System.out.println(i1 == i6); // true
        // 基本类型和包装类进行比较,包装类自动拆箱变为基本类型
        System.out.println(i2 == i6); // true
        // 都指向常量池
        System.out.println(i3 == i6); // false
        // i6在常量池,存在栈内存中;i3是new出的对象,存在堆内存中

}
 		String s0 = "abcdef";
        String s1 = "def";
        String s2 = "abc" + s1; // 涉及到变量,编译器不优化
        String s3 = "abc" + "def"; // 都是常量,编译器优化成abcdef
        String s4 = "abc" + new String ("def"); // 涉及到new对象,编译器不优化
        System.out.println(s0 == s3); // true
        System.out.println(s2 == s3); // false
        System.out.println(s2 == s4); // false
        System.out.println(s3 == s4); // false

不可变对象和字符串:

不可变对象

不可变对象

  • 一旦创建,这个对象(状态、值)不能被更改了
  • 其内在成员变量的值不能修改
  • 包含八个基本的包装类,以及String、BigInteger和BigDecimal

例子:

String a = new String("abc");
String b = a;
System.out.println(b); // abc
a = "def";
System.out.println(b); // abc

输出的两次b都为 abc。
要注意,String是一个不可变对象,它定义的字符串是不可被修改的。
所以,第一句话执行时,在内存中申请了一个值为 abc 的空间(堆内存),对象a指向这块内存。
第二句话执行时,创建了一个对象b指向a指向的空间。
第四句话执行时,在内存中申请了一个值为 def 的空间(常量池,栈内存),对象a更改指向到这块内存,而对象b保持不变。

还有一个例子:

public static void change(String b){
	b = "def";
}

	a = new String("abc");
	change(a);
	System.out.println(a); // abc

在这个例子中,a首先在内存中申请了一个值为 abc 的空间(堆内存),即对象a指向这块内存。
进入change方法后,实参a把自己的指针传给了形参b,之后对象b在内存中申请了一个值为 def 的空间(常量池,栈内存),并指向这块内存。但是a仍然指向原来的空间。

显然,不可变对象不可改变,如果需要新的内容,只能clone或者new一个对象进行修改。
可以通过以下方法保证这个不可变性:

  • 所有的属性都是final和private的
  • 不提供setter方法
  • 类是final的,或者所有方法都是final的

不可变对象的优点:

  • 只读,线程安全
  • 并发读,提高性能
  • 可以重复使用

缺点:

  • 制造垃圾,浪费空间

因为相对不可变对象进行修改,就会开辟新的空间,旧的对象会被搁置,直到垃圾回收。

Java字符串

字符串使用频率极高,是一种典型的不可变对象。
之所以是不可变的,可以通过 String 类的底层源码看出:

  • 在源码中,String 类的字符串内容存储在常量数组 private final char value[],因为关键字 final 的原因,该数组不可被更改
  • 不过这个不可修改指的是字符数组 value 不可以指向新的地址,但是单个字符内容是可以变化的

String定义方法一般有两种:

  • String a = “abc”;
  • String b = new String(“def”);

字符串内容比较:equals方法。
是否指向同一个对象:指针比较 =。

由于String不可修改,因而Java有提供其他的字符串类。
StringBuffer/StringBuilder可修改字符串类,可以通过append方法进行修改。
StringBuffer和StringBuilder的对象都是可变对象。

  • StringBuffer,同步、线程安全、修改快速
  • StringBuilder,不同步、线程不安全、修改极快

看下面的例子:

public static void main(String[] args){
        int n = 50000;
        Calendar t1 = Calendar.getInstance();
        String a = new String("");
        for (int i = 0; i < n; i++)
            a = a + i + ",";
        System.out.println(Calendar.getInstance().getTimeInMillis() - t1.getTimeInMillis());

        Calendar t2 = Calendar.getInstance();
        StringBuffer b = new StringBuffer("");
        for (int i = 0; i < n; i++){
            b.append(i);
            b.append(",");
        }
            
        System.out.println(Calendar.getInstance().getTimeInMillis() - t2.getTimeInMillis());

        Calendar t3 = Calendar.getInstance();
        StringBuilder c = new StringBuilder("");
        for (int i = 0; i < n; i++){
            c.append(i);
            c.append(",");
        }
            
        System.out.println(Calendar.getInstance().getTimeInMillis() - t3.getTimeInMillis());   
}

输出结果:

4154
18
5

可以看出它们之间效率的差距。
所以,如果程序中有大量的字符串加减操作,建议使用StringBuffer/StringBuilder类。

public static void changeValue(int a) {
        a = 10;
    }
    public static void changeValue(String s1) {
        s1 = "def";
    }
    public static void changeValue(StringBuffer s2) {
        s2.append("def");
    }
    public static void changeValue(StringBuilder s1) {
        s1.append("def");
    }
    public static void main(String[] args) {
        int a = 5;
        String b = "abc";
        StringBuffer c = new StringBuffer("abc");
        changeValue(a);
        changeValue(b);
        changeValue(c);
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }

输出结果:

5
abc   
abcdef

可见c最后变为abcdef,因为方法调用时c把指针传给s2,这是一个可变对象,然后直接在对象内容上进行了修改,所以a指向的内容相应也修改。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值