`
nail2008
  • 浏览: 45694 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

SCJP笔记_章三_赋值

    博客分类:
  • SCJP
阅读更多

第三章 赋值

 

 

3.1 栈和堆——快速回顾

这是一篇讲解Java堆与栈的文章:http://neilduan.iteye.com/blog/426830 

考试目标里并没有Java堆栈的内容,但是了解这些内容对我们理解Java工作的机制有很大的帮助。 

  • 实例变量和对象驻留在堆(heap)上。
  • 局部变量驻留在栈(stack)上。 

3.2 字面值、赋值和变量

考试目标1.3 编写代码,将基本类型、数组、枚举和对象作为静态变量、实例变量和局部变量声明、初始化并使用。此外,使用合法的标识符为变量命名。

考试目标7.6 编写代码,正确应用恰当的运算符,包括赋值运算符(限于=、+=、-=)。

 

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,这种情况可以不强制转换,但是下面的情况是必须强制转换的:

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虚拟机中保持加载状态,它们就会一直存在。
  • 实例变量的存在时间次之。它是在创建新实例时创建的,并且会存在到实例被删除时为止。
  • 局部变量再次之。只要方法保持在栈上,它就会存在下去。但是,正如我们很快将看到的,局部变量可以存在下去,还可以“超出作用域”。
  • 仅当代码块在执行时,块变量才会存在。
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个典型的例子:

//错误一:试图从静态上下文中访问一个实例变量。
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。

 

局部数组

必须显式地初始化它,但是在构造数组对象时,其所有元素都会被赋予默认值。

 

将一个引用变量赋予另一个引用变量

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

但是String类型除外。当使用String引用变量修改字符串时,会发生如下事情:

  • 创建一个新字符串(或者在String池中发现一个匹配的String),并保持原来的String对象不变。
  • 然后,将用于修改String的引用(或者通过修改原来的副本,建立一个新的String)赋予全新的String对象。

3.3 向方法传递变量

考试目标7.3 当将对象引用和基本值传入方法中,并执行赋值或关于参数的其他修改操作时,判断对对象引用和基本值的影响。 

 

3.3.1 传递对象引用变量

忍不了了。。理论的东西就是我觉得没什么好说的,可是写书的却能写一大堆,你看完了还觉得确实是这样,但是还是说不出那么一大堆来。

好了,我承认我晕菜了。。总之,传递对象引用变量的实际意思是:告诉引用处的兄弟,我的对象是从哪来的(告诉他这个对象在内存中的地址)。并不是说我把自己的对象传递给了他,或复制了一个给他。

 

3.3.2 Java使用按值传递语法吗

class Test {
 private String i ;
 public String getI() {
  return i;
 }
 public void setI(String i) {
  this.i = i;
 }
 
}
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");  
 }
}

运行的结果是:

1=Jack
1.j=111
2=Mike
2.j=111

我们调用了Test2的doStuff()方法,向它传入了两个参数,一个基本类型,一个引用类型。

可见基本类型是按值传递的,传给方法的是一个副本;

而引用类型是按引用传递的,传给方法的是一个对象的引用地址。 调用与被调用处使用的是同一个地址的对象。

 

3.3.3 传递基本变量

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

 

变量的隐藏:

前面讲static的时候,提到了重定义的概念。这其实是一种隐藏效果,使它看起来就好像在使用被隐藏的变量,但是实际上是使用隐藏变量。

常见的实现隐藏的方法:

  • 直接声明一个相同名称的局部变量,在方法体内
  • 作为变元的一部分声明一个相同名称的局部变量,在变元内 

3.4 数组声明、构建和初始化

考试目标1.3 编写代码,将基本类型、数组、枚举和对象作为静态变量、实例变量和局部变量声明、初始化并使用。此外,使用合法的标识符为变量命名。

 

数组是Java中的对象,它存储多个相同类型的变量。

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

 

3.4.1 声明数组

数组是通过说明它将要保存的元素类型来声明的,元素类型可以是对象或基本类型,类型后面的方括号可以位于标识符的左边或右边。在声明中不要包含长度。

int[] key;
int key[];		//最好不要这样声明
Thread[][] threads;
Thread[] threads[];  //最好不要这样声明

 

3.4.2 构建数组

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

 

构建一维数组:

int[] test;               //声明数组
test = new int[4];  //构建数组

 

构建多维数组:

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

 

3.4.3 初始化数组

初始化数组意味着将内容放入数组中。

int[][] scores = new int[3][];
scores[0] = new int[4];
scores[1] = new int[6];
scores[2] = new int[1];

 

在循环中初始化元素

Dog[] myDogs = new Dog[6];
for(int x=0;x<myDogs.length;x++){
        myDogs[x] = new Dog();
}

 

在一行内声明、构建并初始化数组

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}; //匿名数组初始化时,千万不要指定大小,大小由{}中的元素数来决定

 

合法的数组元素赋值:

前面说到在声明数组的时候只能有一种类型,但其实只要能顺利向上转换的都可以初始化到数组中。比如

//基本数组
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()};

 

一维数组的数组引用赋值

上面说到(合法的数组元素赋值)我们可以将符合数组声明的子类型值初始化给该数组,但是一旦这个数组的类型已经声明了,并不能将他引用赋值给其他的非自身类型的数组。

 

多维数组的数组引用赋值

维数要相等。 

 

3.4.4 初始化块

静态初始化块在类声明时运行,实例初始化块在类实例化时运行。

class Test{
  static int x;
  int y;
  static {x = 7;}  //static init block
  {y=8;}              //instance init block
} 
  • 初始化块的执行次序遵循其出现的次序。
  • 首次加载类时,会运行一次静态初始化块。
  • 每当创建一个类实例时,都会运行实例初始化块。
  • 实例初始化在构造函数的super()调用之后运行。 

3.5 使用包装器类和装箱

考试目标3.1 编写代码,使用基本包装器类(如Boolean、Character、Double、Integer等) ,和/或自动装箱以及拆箱。讨论String、StringBuilder 以及 StringBuffer 类之间的区别。

 

JavaAPI中的包装器类有两个主要目的:

  • 提供一种机制,将基本值“包装”到对象中,从而使基本值能够包含在为对象而保留的操作中。
  • 为基本值提供分类功能。

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表示作为变元。如:

Integer i1 = new Integer(42);
Integer i2 = new Integer("42");

 

valueOf()方法

Integer i1 = Integer.valueOf(42);
Integer i2 = Integer.valueOf("42");
Integer i3 = Integer.valueOf("101011",2);   //2进制

 

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

 

xxxValue()方法:将包装器转换为基本类型

当需要将被包装的数值转换为基本类型时,可使用几个xxxValue()方法之一。如:

Integer i = new Integer(42);
byte b = i.byteValue();

 

parseXxx():将String转换为基本类型。

和valueOf():将String转换为包装器。

double d1 = Double.parseDouble("3.1415");
Double d2 = Double.valueOf("3.1415");

 

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

 

toXxxString()方法(二进制、十六进制、八进制)

Integer和Long包装器类都允许将以10为基数的数值转换为其他基数。

String s = Integer.toHexString(254);
String s = Long.toOctalString(254);

 

3.5.4 自动装箱

Java5开始的新特性:装箱,拆箱。在基本类型和包装器之间使用时,不再需要手动的调用转换的方法,会自动转换。

 

装箱、==和equals()方法

我们知道对于对象类型,如果其引用的地址相同,换句话说它们引用的是同一个对象,我们可以说“A==B为true”;

如果它们的值相等,或者说“在意义上是等价的”,我们可以说“A.equals(B)为true”。

但是,为了节省内存,对于下列包装器对象的两个实例(通过装箱创建),当它们的基本值相同时,它们总是“==”关系: 

  • Boolean
  • Byte
  • 从 \u0000 到 \u007f 的字符(7f是十进制的127)
  • -128~127的 Short 和 Integer

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

 

3.6 重载

考试目标1.5 给定一个代码示例,判断一个方法是否正确地重写或重载了另一个方法,并判断该方法的合法返回值(包括协变式返回值)。

考试目标5.4 给定一个场景,编写代码,声明和/或调用重写方法或重载方法。编写代码,声明和/或调用超类、重写构造函数或重载构造函数。

 

重载带来的难题——方法匹配

可能导致重载有点难于处理的3个因素:

  • 加宽。如当变元为float类型,而方法没有float为变元的,但是有double的,那么将调用double类型的,这就是加宽。但是注意,不能变窄,如果没有匹配类型则无法通过编译
  • 自动装箱。
  • var-arg。

下例用来体会加宽:

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的重载

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
//可见 加宽优先于装箱
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...
//可见 加宽优先于var-arg
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
//可见 装箱优先于var-arg

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 垃圾收集

考试目标7.4 给定一个代码示例,辨别对象从哪个时刻开始复合垃圾收集条件,并判断垃圾收集系统保证什么、不保证什么。理解 Object finalize()方法的行为。

 

3.7.1 内存管理和垃圾收集概述

在C或C++等不提供自动垃圾收集的语言中,手工清空或删除集合数据结构时,逻辑上的一点点缺陷可能会导致少量的内存被错误地回收或丢失。这种少量的内存丢失称为内存泄漏。经过N次的迭代之后,它们可能会导致足够的内存变得不可访问,是程序最终崩溃。

Java的垃圾收集器为内存管理提供了一种自动解决方案。它能使你从必须为应用程序添加所有内存管理逻辑的任务中解脱出来。缺点是不能完全控制它什么时候执行与不执行。

 

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

 

何时运行垃圾收集器?

垃圾收集器受JVM控制,JVM决定什么时候运行垃圾收集器。

在任何情况下都无法保证JVM会答应你的请求,它是自动管理的。

 

如何运行垃圾收集器?

对象在何时开始符合垃圾收集条件?

当没有线程能够访问对象时,该对象就是适合进行垃圾收集的。

 

 

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

 

1、空引用

将对象赋值为“null”,GC就会处理它。

 

2、为引用变量重新赋值

通过设置引用变量引用另一个对象来解除引用变量与对象间的引用关系。

 

3、隔离引用

隔离岛的例子:

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()方法实际上能够导致对象免于被删除。

 

 

 

 

 

 

2
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics