JAVA重难点问题剖析-实例讲解好理解_第1页
JAVA重难点问题剖析-实例讲解好理解_第2页
JAVA重难点问题剖析-实例讲解好理解_第3页
JAVA重难点问题剖析-实例讲解好理解_第4页
JAVA重难点问题剖析-实例讲解好理解_第5页
已阅读5页,还剩37页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

JAVA重难点问题剖析(■)

这次主要剖析以卜.四个问题:

构造函数定义问题、类实例化时的歧义、equals和==、protected成员的访问问题

(1)构造函数前不加void,否则变为方法,只能加可访问性修饰。

publicclassName{

voidNameO(

System,out.printIn(''Method^);

)

publicstaticvoidmain(String[]args){

Namen=newName();

}

)

无输出,去void则输入Method;

(2)如果需要在不同包的类中使用构造函数创建对象必须前面加上public;

二、类实例化时的歧义问题

(1)单类形导入(即importpl.TestC而非importpl.*)时用此类实类化:

(2)完全标示名解决歧义问题pl.TestCb=ne\vpl.TestC()

packagepl;〃定义第一个类

publicclassTestC{

publicTestCO{System.out.printin(^pl^);}

}

packagep2;〃定义第二个类

publicclassTestC{

TestCO{System.out.printIn(,,p2/z);}

packagep2;〃定义第三个类

importpl.*;//(1)

publicclassTestB{

publicstaticvoidmain(String[]args){

TestCc=newTestCO;//(2)

)

输出p2,若要输出pl,可将(1)处改为importpl.TestC或者将(2)处改为pl.TestCb=newpl.TestCO;

三、数值、对象比较问题

java.lang,object类中的equal()方法:

publicbooleanequals(Objectobj){

return(this=obj);

)

java.lang.string类中覆盖object中的equals。方法:

publicbooleanequals(ObjectanObject){

if(this==anObject){

returntrue;

)

if(anObjectinstanceofString){

StringanotherString=(String)anObject;

intn=count;

if(n==anotherString.count){

charvl[]=value;

charv2[]=anotherString.value;

inti=offset;

intj=anotherString.offset;

while(n—!=0){

if(vl[i++]!=v2[j++])

returnfalse;

)

returntrue;

}

)

returnfalse;

)

例子:

classTest{

Strings;

Test(Strings){

this.s=s;

)

)

publicclassFyz{

publicstaticvoidmain(String[]args){

Stringsl=newString(z,hello/,);

Strings2=newStringCzhello/,);

TestTl=newTest("JAVA");

TestT2=newTest("JAVA");

System.out.printin(si.equals(s2));//true:String^Boolean等类的equals覆盖了object的,比

较的不再是对象而是数值

System.out.printin(Tl.equals(T2));//false:没有覆盖object的,所以比较的仍然是对象

System,out.printin(Sl==s2);//false:二二比较的是对象,无论是什么对象;

System.out.println(Tl==T2);//false:同上

T2=T1;〃使引用变量Tl、T2都成为同一对象的别名

System,out.printin(Tl.equals(T2));〃true:对象相同

System.out.printin(T1==T2);//true:同上

)

}

四、若在不同包中子类使用对象引用来访问父类的protected成员,必须将对象实例化为子类对象,而不

能实例为父类对象来访问,即子类只能以实现继承层次中的身份访问其超类的protected成员。相同包子

类中使用对象引用的类型都可以。

packagetestl;

publicclassA{

publicvoidpublicMethod。{System.out.printin("public方法”);}

protectedvoidprotectedMethod(){System,out.printIn("protected方法”);}

voidfriendlyMethod(){System.out.printin("friendly方法〃);}

privatevoidprivateMethodO{System,out.printIn("private方法”);}

packagetestl;

publicclassExtendsAextendsA(

publicstaticvoidmain(String[]args){

AAl=newA();

Al.publicMethod();

Al.protectedMethod();

Al.friendlyMethod();

//Al.privateMethodO;

ExtendsAExtendsAl=newExtendsA0;

ExtendsAl.publicMethod();

ExtendsAl.protectedMethod();

ExtendsAl.friendlyMethod();

//ExtendsAl.privateMethodO;

)

publicvoidprintinfoO{

publicMethod();

protectedMethod();

friendlyMethodO;

//privateMethod();

)

packagetest2;

importtestl.A;

publicclassExtendsAextendsA{

publicstaticvoidmain(String[]args){

AAl=newA();

Al.publicMethod();

//Al.protectedMethod();调用不行

//Al.friendlyMethodO;

//Al.privateMethodO;

ExtendsAExtendsAl=newExtendsA();

ExtendsAl.publicMethodO;

ExtendsAl.protectedMethod();〃可以调用

//ExtendsAl.friendlyMethodO;

//ExtendsAl.privateMethodO;

)

publicvoidprintinfo(){

publicMethodO;

protectedMethod。;〃可以直接调用

//friendlyMethodO;

//privateMethod0;

)

多态与类型转化原理分析:

・、多态性:超类引用在运行时既能代衣超类本身的对象,也能代表其子类的对象的能力。

类的一个成员若想表现多态必须可以被覆盖:

对于成员变量而言,不会发生覆盖现象(会隐藏),在子类出现相同变量的定义时只会隐藏父类变量,因此

不会表现多态。同时变量调用在编译时就会解析,不符合动态绑定的特征;

在成员方法中,静态方法和final方法(private方法)也不会发生覆盖现象(会隐藏),因此也不会表

现多态性。

因此只有除静态方法和final方法以外的方法才会表现多态性。

:、向上类型转化时:

丢失添加的方法和字段,剩余的为:

基类字段

基类静态方法或final方法〃前二者为不能被覆盖的成员,因此保留,无多态性

基类其他方法(若被子类覆盖则为子类覆盖的新方法)

packagetest3;

classoopsuper{

staticStringstr="父类字段〃;

publicvoidpublicMethodO{System.out.printing父类public方法”);}

protectedvoidprotectedMethodO{System.out.printIn("父类protected方法”);}〃(1)

privatevoidprivateMethodO{System.out.printIn(〃父类private方法〃);}

staticvoidstaticMethodO{System.out.printing父类静态方法”);}

}

publicclassoopsubextendsoopsuper{

Stringstr=〃子类字段〃;

publicvoidpublicMethodO{System.out.printIn(〃子类public方法〃);}

protectedvoidprotectedMethodO{System.out.printIn("子类protected方法”);}//(2)

privatevoidprivateMethod(){System,out.prinlln("子类private方法”);}

staticvoidstaticMethodO{System.out.printin(“子类静态方法”);}

publicstaticvoidmain(String[]args){

oopsuperupcast=newoopsub();

System.out.printin(upcast,str);〃方法调用才具有多态性,而域没有多态性

//能被覆盖的方法的行为才仃多态特性

upcast,publicMethodO;

upcast.protectedMethodO;〃若注释掉(1)则有错误;若注释掉(2)则输出:子类protected方法

//不能被覆盖的方法[final方法(含私有方法)、静态方法]的行为不具有多态特性

//upcast.privateMethodO;访问的是父类的私有方法,不能访问,不具有多态现象

upcast.staticMethodO;

}

JAVA重难点问题剖析(二)

Byfengyuzhe发表于2007-12-1412:44:00

0

这次主要剖析以下问题:

抽象类与接口的区别、别名、局部变量的语句块作用域、this构造和super构造

一、抽象类与接口的区别:

*1.抽象类中可以定义所有成员变量(含实例变量和静态变量[含常量])和非空方法,而接口中只能定义

常量和空方法;

*2.抽象类在定义抽象方法时必须加abstract,而在接口中可以加但不需要加;

*3.接口允许多继承:一个接口可以基层多个接口,实现接口的类也可以继承多个接口,但JAVA的类仅

支持单继承。

packagetest;

interfaceobject1{

//intvarl;

//staticintvarls;

intVARI=2;//接口中只能定义常量,等同于finalstaticintVAR1=2;

intinterfaceMethodl();

abstractintinterfaceMethod2();〃接口中的方法其实就是抽象方法,但在接口中一般不加abstra

ct

//intinferfaceMethod3(){)接口中不能含有非抽象方法

}

abstractclassobject2{

intvar2;

staticintvar2s;

finalstaticintVAR2=2;〃抽象类中可以定义变量也可以定义常量

abstractintabstractMethodl();

//intabstractMethod2();空方法必须加abstract修饰符

//abstractintabstractMethod3(){}抽象方法不能指定方法体

voidabstractMethod4(){}〃抽象类中可以含有非抽象方法

)

二、卜.面中的引用变量都是别名

packagetest2;

publicclasstest{

inta;

publictest(inti){

this,a=i;

)

publicstaticvoidmain(String[]args){

testtest1=newtest(10);

testtest2=testl;

System,out.printin(testl.a);

System,out.println(test2.a);

testl=null;

//System.out.println(testl.a);

System,out.printIn(test2.a);

)

三、局部变量的语句块作用域:

语句块{}中所声明的变量的作用域处在声明该变量的语句块中,语句块外部不能访问

publicclasshong{

publicstaticvoidmain(String[]args){

intvarl=1;

{

intvar2=2;

}

System,out.printIn(varl);

//System.out.println(var2);

//var2不能别解析,因为var2的作用域处在语句块。中

)

)

四、this构造和super构造

二者必须位于构造函数的第•行,this。构造用于串链同个类中的构造函数,而super()构造用于激活超

类的构造函数,如果构造函数的第一句不是this。构造或者super()构造,则会插入一条指向超类默认构

造函数的super()调用

classtest{

inti,j;

test(inti,intj){

this,i=i;

this,j=j;

)

)

publicclassSuperDemoextendstest{

intk,1;

SuperDemo(){

thisdl,12);

)

/*错误,插入插入一条指向超类默认构造函数的super()调用

SuperDemo(inti,intj){

this,k=i;

this.1=j;

)

*/

publicstaticvoidmain(String[]args){

SuperDemosd=newSuperDemo();

System,out.print(sd.i+"〃+sd.j+sd.k+sd.1);

)

一.Input和Output

1.stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源。在Java的I0中,

所有的stream(包括Input和Outstream)都包括两种类型:

1.1以字节为导向的stream

以字节为导向的stream,表示以字节为单位从stream中读取或往stream中写入信息。以字节为导向

的stream包括卜.面几种类型:

1)inputstream:

1)ByteArraylnputStream:把内存中的一个缓冲区作为Inputstream使用

2)StringBufferInputStream:把一个String对象作为Inputstream

3)FilelnputStream:把一个文件作为Inputstream,实现对文件的读取操作

4)PipedlnputStream:实现了pipe的概念,主要在线程中使用

5)Sequenceinputstream:把多个Inputstream合并为一个InputStream

2)Outstream

1)ByteArrayOutputStream:把信息存入内存中的一个缓冲区U」

2)FileOutputStream:把信息存入文件中

3)PipedOutputStream:实现了pipe的概念,主要在线程中使用

4)SequenceOutputStream:把多个OutStream合并为OutStream

1.2以Unicode字符为导向的stream

以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入

信息。以Unicode字符为导向的stream包括下面几种类型:

1)InputStream

1)CharArrayReader:与ByteArraylnputStream对应

2)StringReader:StringBufferlnputStream对应

3)FileReader:与FilelnputStream对应

4)PipedReader:与PipedlnputStream对应

2)OutStream

1)CharArrayWrite:与ByteArrayOutputStream对应

2)StringWrite:无与之对应的以字节为导向的stream

3)FileWrite:与FileOutputStream对应

4)PipedWrite:与PipedOutputStream对应

以字符为导向的stream基本上对有与之相对应的以字节为导向的stream。两个对应类实现的功能相同,

字是在操作时的导向不同。如CharArrayReader:和ByteArraylnputStream的作用都是把内存中的

一个缓冲区作为Inputstream使用,所不同的是前者每次从内存中读取一个字节的信息,而后者每次从

内存中读取一个字符。

1.3两种不现导向的stream之间的转换

InputstreamR6ader和OutputstreamReader:把一个以字节为导向的stream转换成一个以字符为

导向的stream。

2.stream添加属性

2.1"为stream添加属性”的作用

运用上面介绍的Java中操作I0的API,我们就可完成我们想完成的任何操作了。但通过

Filterinputstream和FilterOutStream的子类,我们可以为stream添加属性。F面以一个例子来说

明这种功能的作用。

如果我们要往一个文件中写入数据,我们可以这样操作:

FileOutStreamfs=newFileOutStream(Mtest.txt");

然后就可以通过产生的fs对象调用write。函数来往test.txt文件中写入数据了。但是,如果我们想实现

“先把要写入文件的数据先缓存到内存中,再把缓存中的数据写入文件中”的功能时,I二面的API就没有一

个能满足我们的需求了。但是通过Filterinputstream和FilterOutStream的子类,FileOutStream

添加我们所需要的功能。

2.2Filterinputstream的各种类型

2.2.1用于封装以字节为导向的Inputstream

1)DatalnputStream:从stream中读取基本类型(int、char等)数据。

2)BufferedlnputStream:使用缓冲区

3)LineNumberlnputStream:会记录inputstream内的行数,然后可以调用getLineNumber()和

setLineNumber(int)

4)PushbacklnputStream:很少用到,一般用于编译器开发

2.2.2用于封装以字符为导向的Inputstream

1)没有与DatalnputStream对应的类。除非在要使用readLine。时改用BufferedReader,否则使用

DatalnputStream

2)BufferedReader:与BufferedlnputStream对应

3)LineNumberReader:与LineNumberlnputStream对应

4)PushBackReader:与PushbacklnputStream对应

2.3FilterOutStream的各种类型

2.2.3用于封装以字节为导向的Outputstream

1)DatalOutStream:往stream输出基本类型(int、char等)数据。

2)BufferedOutStream:使用缓冲区

3)Printstream:产生格式化输出

2.2.4用于封装以字符为导向的Outputstream

1)BufferedWrite:与对应

2)PrintWrite:与对应

3.RandomAccessFile

1)可通过RandomAccessFile对象完成对文件的读写操作

2)在产生一个对象时,可指明要打开的文件的性质:r,只读:w,只写;rw可读写

3)可以直接跳到文件中指定的位置

4.I/O应用的一个例子

importjava.io.*;

publicclassTestlO{

publicstaticvoidmain(String[]args)

throwsIOException{

〃1.以行为单位从一个文件读取数据

BufferedReaderin=

newBufferedReader(

newFileReader("F:\\nepalon\\TestlO.java"));

Strings,s2=newString();

while((s=in.readLine())!=null)

s2+=s+M\nM;

in.close();

//1b.接收键盘的输入

BufferedReaderstdin=

newBufferedReader(

newInputStreamReader(System.in));

System.out.println(HEnteraline:");

System.out.println(stdin.readLine());

//2.从一个String对象中读取数据

StringReaderin2=newStringReader(s2);

intc;

while((c=in2.read())!=-1)

System.out.printin((char)c);

in2.close();

//3.从内存取出格式化输入

try{

DatalnputStreamin3=

newDatalnputStream(

newByteArraylnputStream(s2.getBytes()));

while(true)

System.out.printin((char)in3.readByte());

)

catch(EOFExceptione){

System.out.println(,'Endofstream");

)

//4.输出到文件

try(

BufferedReaderin4=

newBufferedReader(

newStringReader(s2));

PrintWriterout1=

newPrintWriter(

newBufferedWriter(

newFileWriter("F:\\nepalon\\TestlO.out")));

intlineCount=1;

while((s=in4.readLine())!=null)

Java本质论之关于Java栈与堆的思考

原文地址http:〃/edu/ShowArticle.asD?ArticlelD=1539

1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动

管理栈和堆,程序员不能直接地设置栈或堆。

2.栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈

中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。

堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器

会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

3.Java中的数据类型有两种。

一种是基本类型(primitivetypes),共有8种,即int,short,long,byte,float,double,boolean,

char(注意,并没有string的基本类型)。这种类型的定义是通过诸如inta=3;longb=255L;

的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,

即不是类的引用,这里并没有类的存在。如inta=3;这里的a是一个指向int类型的引用,

指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义

在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈

中。

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

inta=3;

intb=3;

编译器先处理inta=3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值

为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接

着处理intb=3;在创建完b的引用变量后,山于在栈中已经有3这个字面值,便将b直接

指向3的地址。这样,就出现了a与b同时均指向3的情况。

特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一

个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即

刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的

引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会

等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面

值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因

此a值的改变不会影响到b的值。

另一种是包装类数据,如Integer,String,Double等将相应的基本数据类型包装起来的类。

这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需

要动态创建,因此比较灵活,但缺点是要占用更多的时间。4.String是一个特殊的包装类

数据。即可以用Stringstr=newString("abc");的形式来创建,也可以用Stringstr="abe";

的形式来创建(作为对比,在JDK5.0之前,你从未见过Integeri=3;的表达式,因为类与

字面值是不能通用的,除了String。而在JDK5.0中,这种表达式是可以的!因为编译器在

后台进行Integeri=newlnteger(3)的转换)。前者是规范的类的创建过程,即在Java中,

一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如

DateFormat类,可以通过该类的getlnstance()方法来返回一个新创建的类,似乎违反了此

原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过

new()来创建的,而getlnstance()向外部隐藏了此细节。那为什么在Stringstr="abc";中,

并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

5.关于Stringstr="abc"的内部工作。Java内部将此语句转化为以下几个步骤:

⑴先定义一个名为str的对String类的对象引用变量:Stringstr;

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地

址,接着创建一个新的String类的对象o,并将。的字符串值指向这个地址,而且在栈中这

个地址旁边记下这个引用的对象。。如果已经有了值为"abc"的地址,则查找对象。,并返回

o的地址。

⑶将str指向对象o的地址。

值得注意的是,一般String类中字符串值都是直接存值的。但像Stringstr="abc";这种场

合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

Stringstr1="abc";

Stringstr2="abc";

System.out.println(str1==str2);//true

注意,我们这里并不用str1.equals(st⑵;的方式,因为这将比较两个字符串的值是否相等。

==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这

里要看的是,str1与str2是否都指向了同一个对象。

结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向

了这个对象。

我们再来更进一步,将以上代码改成:

Stringstr1="abc";

Stringstr2="abc";

str1="bed";

System.out.println(str1++str2);//bed,abe

System.out.println(str1==str2);//false

这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍

旧指向原来的对象。上例中,当我们将str1的值改为"bed"时,JVM发现在栈中没有存放该

值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM

在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这

个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的

环境中,会带有一定的不良影响。

再修改原来代码:

Stringstr1=HabcH;

Stringstr2="abc";

str1="bed”;

Stringstr3=str1;

System.out.println(str3);//bed

Stringstr4="bed”;

System.out.println(str1==str4);//true

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改

完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以

发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

我们再接着看以下的代码。

Stringstr1=newString("abc");

Stringstr2="abc";

System.out.println(str1==str2);//false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

Stringstr1="abc";

Stringstr2=newString("abc");

System.out.println(str1==str2);//false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独

存值的,即使可栈中的数据相同,也不会与栈中的数据共享。

6.数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装

类都不能更改其内部的值。7.结论与建议:

⑴我们在使用诸如Stringstr="abc";的格式定义类时,总是想当然地认为,我们创建了

String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String

类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,

除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了

一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc”的String

类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

⑵使用Stringstr="abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会

自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于Stringstr=new

String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必

要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部

在这里实现是否应用了这个模式,不得而知。

(3)当比较包装类里面的数值是否相等时,用equals。方法;当测试两个包装类的引用是否

指向同一个对象时,用==。

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用

StringBuffer类,以提高程序效率。

Java本质论之关于Java栈与堆的思考

原文地址http:〃/edu/ShowArticle.asp?ArticlelD=1539

1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动

管理栈和堆,程序员不能直接地设置栈或堆。

2.栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈

中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。

堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器

会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

3.Java中的数据类型有两种。

一种是基本类型(primitivetypes),共有8种,即int,short,long,byte,float,double,boolean,

char(注意,并没有string的基本类型)。这种类型的定义是通过诸如inta=3;longb=255L;

的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,

即不是类的引用,这里并没有类的存在。如inta=3;这里的a是一个指向int类型的引用,

指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义

在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈

中。

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

inta=3;

intb=3;

编译器先处理inta=3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值

为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接

着处理intb=3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接

指向3的地址。这样,就出现了a与b同时均指向3的情况。

特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一

个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即

刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的

引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会

等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面

值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因

此a值的改变不会影响到b的值。

另一种是包装类数据,如Integer,String,Double等将相应的基本数据类型包装起来的类。

这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需

要动态创建,因此比较灵活•,但缺点是要占用更多的时间。4.String是一个特殊的包装类

数据。即可以用Stringstr=newString("abc");的形式来创建,也可以用Stringstr="abc";

的形式来创建(作为对比,在JDK5.0之前,你从未见过Integeri=3;的表达式,因为类与

字面值是不能通用的,除了String。而在JDK5.0中,这种表达式是可以的!因为编译器在

后台进行Integeri=newlnteger(3)的转换)。前者是规范的类的创建过程,即在Java中,

一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如

DateFormat类,可以通过该类的getlnstance()方法来返回一个新创建的类,似乎违反了此

原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过

new()来创建的,而getlnstance()向外部隐藏了此细节。那为什么在Stringstr="abc";中,

并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

5.关于Stringstr="abc"的内部工作。Java内部将此语句转化为以下几个步骤:

⑴先定义一个名为str的对String类的对象引用变量:Stringstr;

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地

址,接着创建一个新的String类的对象o,并将。的字符串值指向这个地址,而且在栈中这

个地址旁边记下这个引用的对象。。如果已经有了值为"abc"的地址,则查找对象。,并返回

o的地址。

(3)将str指向对象o的地址。

值得注意的是,一般String类中字符串值都是直接存值的。但像Stringstr="abc";这种场

合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

Stringstr1="abc";

Stringstr2="abc";

System.out.println(str1==str2);//true

注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。

==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这

里要看的是,str1与str2是否都指向了同个对象。

结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向

了这个对象。

我们再来更进一步,将以上代码改成:

Stringstr1="abc";

Stringstr2="abc";

str1="bed";

System.out.println(str1++str2);//bed,abc

System.out.println(str1==str2);//false

这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍

旧指向原来的对象。上例中,当我们将str1的值改为"bed"时,JVM发现在栈中没有存放该

值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM

在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这

个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的

环境中,会带有一定的不良影响。

再修改原来代码:

Stringstr1="abc";

Stringstr2=,'abcH;

str1=“bed”;

Stringstr3=str1;

System.out.println(str3);//bed

Stringstr4="bed”;

System.out.println(str1==str4);//true

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改

完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以

发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

我们再接着看以下的代码。

Stringstr1=newString("abc");

Stringstr2="abc";

System.out.println(str1==str2);//false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

Stringstr1=,'abcH;

Stringstr2=newString("abc");

System.out.println(str1==str2);//false

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独

存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

6.数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装

类都不能更改其内部的值。7.结论与建议:

⑴我们在使用诸如Stringstr="abc";的格式定义类时,总是想当然地认为,我们创建了

String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String

类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,

除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了

一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String

类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

⑵使用Stringstr="abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会

自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于Stringstr=new

String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必

要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部

在这里实现是否应用了这个模式,不得而知。

⑶当比较包装类里面的数值是否相等时,用equals。方法;当测试两个包装类的引用是否

指向同一个对象时,用==。

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用

StringBuffer类,以提高程序效率。

入门必看的5个JAVA经典实例

1-个饲养员给动物喂食物的例子体现JAVA中的面向对象思想,接口(抽象类)的用处

packagecom.softeem.demo;

/**

*@authorleno

*动物的接口

*/

interfaceAnimal{

publicvoideat(Foodfood);

)

/**

authorleno

*一种动物类:猫

*/

classCatimplementsAnimal{

publicvoideat(Foodfood){

System.out.println("小猫吃"+food.getName());

)

I

/**

*@authorleno

木•种动物类:狗

*/

classDogimplementsAnimal{

publicvoideat(Foodfood){

System.out.println("小狗啃"+food.getName());

)

}

/**

authorleno

*食物抽象类

*/

abstractclassFood(

protectedStringname;

publicStringgetName(){

returnname;

publicvoidsetName(Stringname){

=name;

authorleno

*•种食物类:鱼

*/

classFishextendsFood{

publicFish(Stringname){

=name;

)

}

/**

*@authorleno

*一种食物类:骨头

*/

classBoneextendsFood{

publicBone(Stringname){

=name;

/**

*@authorleno

*饲养员类

*

*/

classFeeder(

/**

*饲养员给某种动物喂某种食物

*@paramanimal

*@paramfood

*/

publicvoidfeed(Animalanimal,Foodfood){

animal.eat(food);

authorleno

*测试饲养员给动物喂食物

*/

publicclassTestFeeder{

publicstaticvoidmain(String[]args){

Feederfeeder=newFeeder();

Animalanimal=newDog();

Foodfood=newBone("肉骨头)

feeder.fecdfanimal.food);〃给狗喂肉骨头

animal=newCat();

food=newFish("鱼”);

feeder.feed(animal,food);〃给猫喂鱼

}

}

2.做一个单子模式的类,只加载一次属性文件

packagecom.softeem.demo;

importjava.io.FilelnputStream;

importjava.io.FileNotFoundException:

importjava.io.IOExccption;

importjava.io.InputStream;

importjava.util.Properties;

/**

*@authorleno单子模式,保证在整个应用期间只加载一次配置属性文件

*/

publicclassSingleton{

privatestaticSingletoninstance;

privatestaticfinalStringCONFIG_FILE_PATH="E:\\perties'1;

privatePropertiesconfig;

privateSingleton(){

config=newProperties();

InputStreamis;

try(

is=newFileInputStream(CONFIG_FILE_PATH);

config.load(is);

is.close();

}catch(FileNotFoundExceptione){

//TODOAuto-generatedcatchblock

e.printStackTrace();

}catch(lOExceptione){

//TODOAuto-generatcdcatchblock

e.printStackTrace();

1

)

publicstaticSingletongctlnstance(){

if(instance==null){

instance=newSingleton();

)

returninstance;

publicPropertiesgetConfigO{

returnconfig;

)

publicvoidsetConfig(Propertiesconfig){

this.config=config;

)

1

3.用JAVA中的多线程示例银行取款问题

packagecom.softeem.demo;

/**

*@authorleno

*账户类

*默认有余额,可以取款

*/

classAccount{

privatefloatbalance=1000;

publicfloatgetBalance(){

returnbalance;

)

publicvoidsetBalance(floatbalance){

this.balance=balance;

/**

*取款的方法需要同步

*@parammoney

*/

publicsynchronizedvoidwithdrawals(floatmoney){

if(balance>=money){

System.out.printin("被取走"+money+"元!");

try(

Thread.sleep(1000);

}catch(InterruptedExceptione){

//TODOAuto-generatedcatchblock

e.printStackTrace();

)

balance-=money;

)else{

System.out.println("对不起,余额不足!)

authorleno

银行卡

*/

classTestAccount1extendsThread{

privateAccountaccount;

publicTestAccount1(Accountaccount){

this.account=account;

}

@Override

publicvoidrun(){

account.withdrawals(800);

System.out.println("余额为:"+account.getBalance()+"元!”);

*@authorleno

*存折

*/

classTestAccount?extendsThread{

privateAccountaccount;

publicTestAccount2(Accountaccount){

this.account=account;

@Override

publicvoidrun(){

account.withdrawals(700);

System.out.println("余额为:"+account.getBalance()+"元!”);

}

I

publicclassTest{

publicstaticvoidmain(String|]args){

Accountaccount=newAccount();

TestAccountltestAccount1=newTestAccount1(account);

testAccount1.start();

TestAccount2testAccount2=newTestAccount2(account);

testAccount2.starl();

)

)

4.用JAVA中的多线程示例生产者和消费者问题

packagecom.softeem.demo;

classProducerimplementsRunnable{

privateSyncStackstack;

publicProducer(SyncStackstack){

this.stack=stack;

publicvoidrun(){

for(inti=0;i<stack.getProducts().length;i++){

Stringproduct="产品"+i;

stack.push(product)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论