`
sunmiracle
  • 浏览: 14801 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

第三章 赋值

    博客分类:
  • SCJP
阅读更多
第三章 赋值


3.1 栈和堆——快速回顾
实例变量和对象驻留在堆(heap)上。
局部变量驻留在栈(stack)上。
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢


3.2 字面值、赋值和变量

3.2.1 所有基本类型字面值
基本类型字面值就是我们在给基本类型赋值时“=”后面的“值”。

整形字面值:Java有3种表示整数的方法:十进制、八进制、十六进制。
十进制字面值:还用说吗?
八进制字面值:“0”前缀,如:06,014,077
十六进制字面值:“0x”或“0X”前缀,如:0x001F,0X23Df(不区分大小写)
字面值默认被定义为int型,如加后缀“L”或“l”,则为long型。
浮点字面值:默认为double类型(64位),可以不加“D”后缀。在数字后加“F”或“f”后缀,标识为float型(32位)。

布尔字面值:Java的boolean字面值只能为true或false,不能为数字。

字符字面值:字符字面值有以下集中表示方式:
单引号内的单个字符。如:'a','@'
单引号内的Unicode值。如:'\u004E'
字符实际上是一个16位无符号整数(小于等于65535),如:0x892,982。
用转义字符表示不能作为字面值键入的字符。如:'\"','\n'
字符串字面值:String对象值的源代码表示。它并不是基本类型。如:String str = "Hello Java";

3.2.2 赋值运算符
基本变量赋值:
我们知道每种字面值都会有默认的类型,在赋值时常常要注意声明的类型是否跟赋的字面值类型匹配。
如 byte = 27,这种情况可以不强制转换,但是下面的情况是必须强制转换的:
Java代码
byte a = 3; 
byte b = 3; 
byte c = (byte)(a+b); //必须强制转换 

基本类型的强制转换:
隐式的强制转换会自动实现,比如short转int,int转long。
而当大转小、浮点转整型的时候,就需要显式的强制转换:
将浮点数强制转换整数类型时,小时点后面的所有位都将丢失。
将长类型强制转换短类型时,如果字面值超过短类型的范围,会阶段超出的高位。
浮点数赋值:默认为double,如为float,需要强制转换或在字面值后加“F”后缀。

赋予变量一个过大的字面值:同长类型转换短类型时的结果。

将一个基本变量赋予另一个基本变量:则它们有完全相等的副本,但是并不表示他们共享同一个副本。

引用变量赋值:
Button b = new Button();   做了什么?
建立一个名为b的Button类型的引用变量
在堆上创建一个新的Button对象
将新创建的Button对象赋予引用变量b
变量作用域:
静态变量具有最长的作用域。它是在加载类时创建的,并且只要类在Java虚拟机中保持加载状态,它们就会一直存在。
实例变量的存在时间次之。它是在创建新实例时创建的,并且会存在到实例被删除时为止。
局部变量再次之。只要方法保持在栈上,它就会存在下去。但是,正如我们很快将看到的,局部变量可以存在下去,还可以“超出作用域”。
仅当代码块在执行时,块变量才会存在。
Java代码
class Layout {      // 类 
    static int s = 343;     // s是静态变量 
    int x;      // x是实例变量 
    { 
        x = 7; 
        int x2 = 5;    //x2是初始块变量,属于局部变量 
    } 
    Layout() { 
        x += 8; 
        int x3 = 6;    //x3是构造函数变量,属于局部变量 
    } 
    void doStuff() { 
        int y = 0;      //y是局部变量 
        for (int z = 0; z < 4; z++) {   //z是块变量 
            y += z + x; 
        } 
    } 


作用域错误最常见的原因是:试图访问一个不在作用域中的变量。下面是3个典型的例子:
Java代码
//错误一:试图从静态上下文中访问一个实例变量。 
class ScopeErrors{ 
    int x = 5 ; 
    public static void main(String[] args){ 
        x++;    //编译错误,x是一个实例变量 
    } 

 
//错误二:试图从嵌套方法访问局部变量。 
class ScopeErrors2{ 
    public static void main(String[] args){ 
        ScopeErrors2 s = new ScopeErrors2(); 
        s.go(); 
    } 
    void go(){ 
        int y = 5; 
        go2(); 
        y++; 
    } 
    void go2(){ 
        y++;    // 编译错误,y是go()的局部变量 
    }    
 
 
//错误三:在代码块完成后试图使用块变量 
    void go3(){ 
        for(int z = 0;z<5;z++){ 
            boolean test = false; 
            if(z == 3){ 
                test = true; 
                break; 
            } 
        } 
        System.out.print(test); //编译错误,test的生命周期已经结束了。 
    } 


3.2.3 使用未初始化或未赋值的变量或数组元素

基本类型和对象类型实例变量:
每次创建一个新的实例时,实例变量都会被初始化为一个默认值但在对象的超类构造函数完成之后给它赋予一个显式值

基本类型和对象类型的默认值表

变量类型 默认值
对象引用 null
byte,short,int,long 0
float,double 0.0
boolean false
char '\u0000'

数组实例变量如果未初始化变量,则按照上表给它的每个项赋一个相应的默认值
 
3.2.4 局部(栈、自动)基本变量和对象变量

局部基本变量
局部变量总是必须在使用它们之前初始化。Java不会为局部变量赋予默认值,必须显式初始化。

局部对象引用
同上,必须显式的赋值为null。

局部数组
必须显式地初始化它,但是在构造数组对象时,其所有元素都会被赋予默认值。
实例变量数组对象不需要声明就可以有默认值 null,如果进行初始化后就是数组类型的初始值(int 就是0,对象数组就都是Null)。局部变量数组对象必须声明,也就是要(new 数组),不进行显示声明会编译出错。

将一个引用变量赋予另一个引用变量
两个引用将引用同一个实例,当对一个进行修改时,另一个也变化。这与“将一个基本变量赋予另一个基本变量”是不同的。

但是String类型除外。当使用String引用变量修改字符串时,会发生如下事情:
创建一个新字符串(或者在String池中发现一个匹配的String),并保持原来的String对象不变。
然后,将用于修改String的引用(或者通过修改原来的副本,建立一个新的String)赋予全新的String对象。
3.3 向方法传递变量

3.3.1 传递对象引用变量

3.3.2 Java使用按值传递语法吗
Java代码
class Test { 
private String i ; 
public String getI() { 
  return i; 

public void setI(String i) { 
  this.i = i; 

  

Java代码
class Test2{ 
public static void main(String[] args){ 
  int j = 111; 
  Test t1 = new Test(); 
  t1.setI("Jack"); 
  System.out.println("1="+t1.getI()); 
  System.out.println("1.j="+j); 
  Test2 t2 = new Test2(); 
  t2.doStuff(j,t1); 
  System.out.println("2="+t1.getI()); 
  System.out.println("2.j="+j); 
}  
private void doStuff(int j,Test t){ 
  j = 222; 
  t.setI("Mike");   


运行的结果是:
Java代码
1=Jack 
1.j=111 
2=Mike 
2.j=111 
我们调用了Test2的doStuff()方法,向它传入了两个参数,一个基本类型,一个引用类型。
可见基本类型是按值传递的,传给方法的是一个副本;
而引用类型是按引用传递的,传给方法的是一个对象的引用地址。 调用与被调用处使用的是同一个地址的对象。

3.3.3 传递基本变量
同上,基本变量按值传递,传递该变量内的一个位副本。

变量的隐藏:
前面讲static的时候,提到了重定义的概念。这其实是一种隐藏效果,使它看起来就好像在使用被隐藏的变量,但是实际上是使用隐藏变量。
常见的实现隐藏的方法:
直接声明一个相同名称的局部变量,在方法体内
作为变元的一部分声明一个相同名称的局部变量,在变元内
3.4 数组声明、构建和初始化

数组是Java中的对象,它存储多个相同类型的变量。
数组能够保存基本类型或对象引用,但是数组本身总是堆中的对象,即使数组被声明为用以保存基本类型的元素也是如此。

3.4.1 声明数组
数组是通过说明它将要保存的元素类型来声明的,元素类型可以是对象或基本类型,类型后面的方括号可以位于标识符的左边或右边。在声明中不要包含长度。
Java代码
int[] key; 
int key[];      //最好不要这样声明 
Thread[][] threads; 
Thread[] threads[];  //最好不要这样声明 

3.4.2 构建数组
构建数组意味着在堆(所有对象都存在于其中)上创建数组对象——即在数组类型上执行一次new操作。并且要指定数组大小。

构建一维数组:
Java代码
int[] test;               //声明数组 
test = new int[4];  //构建数组 

构建多维数组:
Java代码
int [] [] myArray = new int [3] []; 
//只声明了一维的大小,这是允许的。 
//换句话说我们告诉JVM,myArray是由3个int[]组成的,这就可以通过编译了。  

3.4.3 初始化数组
初始化数组意味着将内容放入数组中。
Java代码
int[][] scores = new int[3][]; 
scores[0] = new int[4]; 
scores[1] = new int[6]; 
scores[2] = new int[1]; 

在循环中初始化元素
Java代码
Dog[] myDogs = new Dog[6]; 
for(int x=0;x<myDogs.length;x++){ 
        myDogs[x] = new Dog(); 


在一行内声明、构建并初始化数组
Java代码
int[] dots = {5,6,7,8}; 
Dog[] myDogs = {new Dog("Clover"), new Dog("Aiko")}; 
int[][] scores = {{1,3,4},{4,3,1,3},{3}};  

构建和初始化匿名数组
int[] array = new int[] {3,4,5}; //<span style="color: #ff0000;">匿名数组初始化时,千万不要指定大小,大小由{}中的元素数来决定</span>。 

合法的数组元素赋值:
前面说到在声明数组的时候只能有一种类型,但其实只要能顺利向上转换的都可以初始化到数组中。比如
Java代码
//基本数组 
int[] array = new int[5]; 
byte b = 4; 
char c = 'c'; 
short s = 7; 
array[0] = b; 
array[1] = c; 
array[2] = s; 
 
//对象引用数组 
class Car{} 
class Ferrari extends Car{} 
Car[] myCars = {new Car(),new Ferrari()}; 

一维数组的数组引用赋值
上面说到(合法的数组元素赋值)我们可以将符合数组声明的子类型值初始化给该数组,但是一旦这个数组的类型已经声明了,并不能将他引用赋值给其他的非自身类型的数组。
不能将byte类型的数组赋给int类型,但是对象数组的子类之间是可以的。也就是一定要通过IS-A测试
多维数组的数组引用赋值
维数要相等。

3.4.4 初始化块
静态初始化块在类声明时运行,实例初始化块在类实例化时运行。
Java代码
class Test{ 
  static int x; 
  int y; 
  static {x = 7;}  //static init block 
  {y=8;}              //instance init block 
}  
初始化块的执行次序遵循其出现的次序。
首次加载类时,会运行一次静态初始化块。
每当创建一个类实例时,都会运行实例初始化块。
实例初始化在构造函数的super()调用之后运行。
先是静态块,然后super,就是看父类的实例初始化快,然后在看构造函数的初始化。
3.5 使用包装器类和装箱
3.5.1 包装器类概述

基本类型 包装器类 构造函数变元
boolean Boolean boolean或String
byte Byte byte或String
char Character char
double Double double或String
float Float float、double或String
int Integer int或String
long Long long或String
short Short short或String

3.5.2 创建包装器对象
包装器构造函数
除Character之外,所有包装器类都提供两个构造函数:一个以要构建的基本类型作为变元,另一个以要构建类型的String表示作为变元。如:
Java代码
Integer i1 = new Integer(42); 
Integer i2 = new Integer("42"); 

valueOf()方法
Java代码
Integer i1 = Integer.valueOf(42); 
Integer i2 = Integer.valueOf("42"); 
Integer i3 = Integer.valueOf("101011",2);   //2进制 

3.5.3 使用包装器转换实用工具

xxxValue()方法:将包装器转换为基本类型
当需要将被包装的数值转换为基本类型时,可使用几个xxxValue()方法之一。如:
Java代码
Integer i = new Integer(42); 
byte b = i.byteValue(); 

parseXxx():将String转换为基本类型。
和valueOf():将String转换为包装器。
Java代码
double d1 = Double.parseDouble("3.1415"); 
Double d2 = Double.valueOf("3.1415"); 

toString()方法:方法返回String,其值为包装在对象内的基本类型值。

toXxxString()方法(二进制、十六进制、八进制)
Integer和Long包装器类都允许将以10为基数的数值转换为其他基数。
Java代码
String s = Integer.toHexString(254); 
String s = Long.toOctalString(254); 

3.5.4 自动装箱

装箱、==和equals()方法
我们知道对于对象类型,如果其引用的地址相同,换句话说它们引用的是同一个对象,我们可以说“A==B为true”;
如果它们的值相等,或者说“在意义上是等价的”,我们可以说“A.equals(B)为true”。
但是,为了节省内存,对于下列包装器对象的两个实例(通过装箱创建),当它们的基本值相同时,它们总是“==”关系:
Boolean
Byte
从 \u0000 到 \u007f 的字符(7f是十进制的127)
-128~127的 Short 和 Integer

装箱能用在什么地方:只要能够正常使用基本变量或包装对象,装箱和拆箱都适用。

3.6 重载

重载带来的难题——方法匹配
可能导致重载有点难于处理的3个因素:
加宽。如当变元为float类型,而方法没有float为变元的,但是有double的,那么将调用double类型的,这就是加宽。但是注意,不能变窄,如果没有匹配类型则无法通过编译。
自动装箱。
var-arg。
下例用来体会加宽:
Java代码
public class EasyOver { 
    static void go(int x){System.out.print("int ");} 
    static void go(long x){System.out.print("long ");} 
    static void go(double x){System.out.print("double ");} 
     
    public static void main(String[] args){ 
        byte b = 5; 
        short s = 5; 
        long l = 5; 
        float f = 5.0f; 
         
        EasyOver.go(b); 
        EasyOver.go(s); 
        EasyOver.go(l); 
        EasyOver.go(f); 
    } 
}//结果是int int long double 

带有装箱和var-arg的重载
Java代码
public class AddBoxing { 
    static void go(Integer x){System.out.println("Integer");} 
    //static void go(long x){System.out.println("long");} 
    public static void main(String[] args){ 
        int i = 5; 
        go(i); 
    } 

//运行结果:long 
//如果注释掉go(long x)方法,运行结果:Integer 
//可见 <span style="color: #0000ff;">加宽优先于装箱</span> 
Java代码
public class AddVarargs { 
    static void go(int x , int y){System.out.println("int,int");} 
    static void go(int... x){System.out.println("int...");} 
    public static void main(String[] args){ 
        int i = 5; 
        go(i,i); 
    } 

//运行结果:int,int 
//如果没有go(int x,int y)方法,则运行结果为int... 
//可见 <span style="color: #0000ff;">加宽优先于var-arg</span> 
Java代码
public class BoxOrVararg { 
    static void go(Byte x,Byte y){System.out.println("Byte,Byte");} 
    static void go(byte... x){System.out.println("byte...");} 
    public static void main(String[] args){ 
        byte b = 5; 
        go(b,b); 
    } 

//运行结果:Byte,Byte 
//可见 <span style="color: #0000ff;">装箱优先于var-arg</span> 
JVM选择重载方法的优先顺序是:加宽、装箱、var-arg。

加宽引用变量
对于对象,也就是引用变量,引用加宽依赖于继承,换句话说,依赖于IS-A测试。
由于这种依赖,加宽IS-A关系的引用变量是合法的;但是也由于IS-A依赖,从一个包装器类加宽到另一个包装器类是非法的。包装器类之间是平等的。如AddBoxing中,如“i”是short类型是无法通过编译的。

使用加宽、装箱和var-arg的重载方法的几条规则:
基本类型的加宽使用可能的“最小”方法变元。
当分别使用时,装箱与var-arg都与重载兼容。
不能从一种包装器类型加宽到另一种包装器类型(IS-A测试会失败)。
不能(JVM自动)先加宽,后装箱(int不能变成Long)。
可以先装箱,后加宽(int可以通过Integer变成Object)。
可以组合使用var-arg与加宽或装箱。
3.7 垃圾收集
当对象没有任何对它的可到达引用时,它就符合垃圾收集条件了。如果有两个引用,一个引用为null,另一个不是,那个这个对象不符合垃圾收集条件。

3.7.1 内存管理和垃圾收集概述
在C或C++等不提供自动垃圾收集的语言中,手工清空或删除集合数据结构时,逻辑上的一点点缺陷可能会导致少量的内存被错误地回收或丢失。这种少量的内存丢失称为内存泄漏。经过N次的迭代之后,它们可能会导致足够的内存变得不可访问,是程序最终崩溃。
Java的垃圾收集器为内存管理提供了一种自动解决方案。它能使你从必须为应用程序添加所有内存管理逻辑的任务中解脱出来。缺点是不能完全控制它什么时候执行与不执行。

3.7.2 Java垃圾收集器概述(Garbage Collection)

何时运行垃圾收集器?
垃圾收集器受JVM控制,JVM决定什么时候运行垃圾收集器。
在任何情况下都无法保证JVM会答应你的请求,它是自动管理的。

如何运行垃圾收集器?
对象在何时开始符合垃圾收集条件?
当没有线程能够访问对象时,该对象就是适合进行垃圾收集的。


3.7.3 编写代码,显式地使对象复合垃圾收集条件

1、空引用
将对象赋值为“null”,GC就会处理它。

2、为引用变量重新赋值
通过设置引用变量引用另一个对象来解除引用变量与对象间的引用关系。

3、隔离引用
隔离岛的例子:
Java代码
public class Island { 
    Island i; 
    public static void main(String[] args){ 
        Island i2 = new Island(); 
        Island i3 = new Island(); 
        Island i4 = new Island(); 
         
        i2.i = i3; 
        i3.i = i4; 
        i4.i = i2; 
         
        i2 = null; 
        i3 = null; 
        i4 = null; 
    } 

看上面的代码,3个Island对象都拥有实例变量,它们相互引用,但是它们指向外界的连接已经被设置为null。这3个对象都符合垃圾收集条件。

4、强制执行垃圾收集
实际上,只能建议由JVM执行垃圾收集,根本不能保证JVM从内存中实际删除所有不使用的对象。
“请求”垃圾收集的最简单方法:System.gc();(查看Runtime类 Runtime.gc())

5、垃圾收集前进行清理——finalize()方法
建议一般情况下根本不要重写finalize()方法。
对于任何给定的对象,finalize()方法最多只会被垃圾收集器调用一次。
调用finalize()方法实际上能够导致对象免于被删除。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics