SCJP笔记_章三_赋值

第三章 赋值java

 

 

3.1 栈和堆——快速回顾数组

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

考试目标里并无Java堆栈的内容,可是了解这些内容对咱们理解Java工做的机制有很大的帮助。 函数

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

3.2 字面值、赋值和变量工具

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

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

 

3.2.1 全部基本类型字面值this

基本类型字面值就是咱们在给基本类型赋值时“=”后面的“值”。spa

 

整形字面值: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 编写代码,显式地使对象复合垃圾收集条件

 

一、空引用

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

 

二、为引用变量从新赋值

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

 

三、隔离引用

隔离岛的例子:

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个对象都复合垃圾收集条件。

 

四、强制执行垃圾收集

实际上,只能建议由JVM执行垃圾收集,根本不能保证JVM从内存中实际删除全部不使用的对象。

“请求”垃圾收集的最简单方法:System.gc();(查看Runtime类 Runtime.gc())

 

五、垃圾收集前进行清理——finalize()方法

建议通常状况下根本不要重写finalize()方法。

  • 对于任何给定的对象,finalize()方法最多只会被垃圾收集器调用一次。
  • 调用finalize()方法实际上可以致使对象免于被删除。
相关文章
相关标签/搜索