和sun程序员的一次聊天

来源:互联网 发布:pca分析软件 编辑:程序博客网 时间:2024/05/18 02:47

 

 

今天在CSDN的论坛上看了篇小说《谋划》,里面主人公和本人算是同行吧,搞互联网的。不过搞挨踢的文笔有那样很不错了,赞一下,各位闲下来时倒是可以拿来开心一下!在论坛里我看到有人贴出了高级java(说里面技术大拿很多),本人写java也两年了,虽然是嫩了点,现在在空中网互联网事业部工作!反正有点闲,于是我加了这个群,没想到群主很特别,并没有第一次同意了我,而是以提问的方式拒绝了我。我也回答了几个问题,后来才得知这哥们83年的,在sun工作2年了,负责研发存储科技有关的Java软件,果真是牛人!今天也让我受教育了,特别是工作中有些细节容易被忽视,下面就问及的几个问题我也讨论下,也给自己提个醒钟:

 

1.       JAVAString理论上最大长度是多少?

2.       JAVA对象的串行化能存储在静态变量中吗?

3.       如果把System.exit(0)放在catch块中,finally块是否还会被运行?

4.       JavaSystem.gc()是否能够启动垃圾回收?

5.       请说出JNI的很多缺点中的一个缺点?

6.       Java里的Annotation(注释),能被子类继承吗?

。。。。。。

 

咋一看似乎也是很平常的问题,不过仔细一想有些问题给你30(不能用google)你未必能很快答出来:)

 

找了几篇和问题相关的不错的帖子

1.    JAVAString理论上最大长度是多少?

要理解 javaString的运作方式,必须明确一点:String是一个非可变类(immutable)。什么是非可变类呢?简单说来,非可变类的实例是不能被修改的,每个实例中包含的信息都必须在该实例创建的时候就提供出来,并且在对象的整个生存周期内固定不变。java为什么要把String设计为非可 变类呢?你可以问问 james Gosling :)。但是非可变类确实有着自身的优势,如状态单一,对象简单,便于维护。其次,该类对象对象本质上是线程安全的,不要求同步。此外用户可以共享非可变对象,甚至可以共享它们的内部信息。(详见 《Effective javaitem13)。String类在java中被大量运用,甚至在class文件中都有其身影,因此将其设计为简单轻便的非可变类是比较合适的。

一、创建。
   
好了,知道String是非可变类以后,我们可以进一步了解String的构造方式了。创建一个Stirng对象,主要就有以下两种方式:

java 代码

  1. String str1 = new String("abc");    
  2. Stirng str2 = "abc";  

     虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在内部维护的stringspool中通过String equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象; 如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 Stringintern方法。看下面的例子:

java 代码

  1. String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  2.   
  3.  //jvm strings pool中找不到值为“abc”的字符串,因此   
  4.  //在堆上创建一个String对象,并将该对象的引用加入至strings pool   
  5.  //此时堆上有两个String对象   
  6. Stirng str2 = "abc";   
  7.   
  8.  if(str1 == str2){   
  9.          System.out.println("str1 == str2");   
  10.  }else{   
  11.          System.out.println("str1 != str2");   
  12.  }   
  13.   //打印结果是 str1 != str2,因为它们是堆上两个不同的对象   
  14.   
  15.   String str3 = "abc";   
  16.  //此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equels “abc”   
  17.  //因此直接返回str2指向的对象给str3,也就是说str2str3是指向同一个对象的引用   
  18.   if(str2 == str3){   
  19.          System.out.println("str2 == str3");   
  20.   }else{   
  21.          System.out.println("str2 != str3");   
  22.   }   
  23.  //打印结果为 str2 == str3  

   再看下面的例子:

java 代码

  1. String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  2.   
  3. str1 = str1.intern();   
  4. //程序显式将str1放到strings pool中,intern运行过程是这样的:首先查看strings pool   
  5. //有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用加入至   
  6. //strings pool中。执行完该语句后,str1原来指向的String对象已经成为垃圾对象了,随时会   
  7. //GC收集。   
  8.   
  9. //此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equels “abc”   
  10. //因此直接返回str1指向的对象给str2,也就是说str2str1引用着同一个对象,   
  11. //此时,堆上的有效对象只有一个。   
  12. Stirng str2 = "abc";   
  13.   
  14.  if(str1 == str2){   
  15.          System.out.println("str1 == str2");   
  16.  }else{   
  17.          System.out.println("str1 != str2");   
  18.  }   
  19.   //打印结果是 str1 == str2   
  20.   

 

    为什么jvm可以这样处理String对象呢?就是因为String的非可变性。既然所引用的对象一旦创建就永不更改,那么多个引用共用一个对象时互不影响。


二、串接(Concatenation)。
     java
程序员应该都知道滥用String的串接操作符是会影响程序的性能的。性能问题从何而来呢?归根结底就是String类的非可变性。既然 String对象都是非可变的,也就是对象一旦创建了就不能够改变其内在状态了,但是串接操作明显是要增长字符串的,也就是要改变String的内部状 态,两者出现了矛盾。怎么办呢?要维护String的非可变性,只好在串接完成后新建一个String 对象来表示新产生的字符串了。也就是说,每一次执行串接操作都会导致新对象的产生,如果串接操作执行很频繁,就会导致大量对象的创建,性能问题也就随之而来了。
   
为了解决这个问题,jdkString类提供了一个可变的配套类,StringBuffer。使用StringBuffer对象,由于该类是可变的,串 接时仅仅时改变了内部数据结构,而不会创建新的对象,因此性能上有很大的提高。针对单线程,jdk 5.0还提供了StringBuilder类,在单线程环境下,由于不用考虑同步问题,使用该类使性能得到进一步的提高。

三、String的长度
  
我们可以使用串接操作符得到一个长度更长的字符串,那么,String对象最多能容纳多少字符呢?查看String的源代码我们可以得知类String中 是使用域 count 来记录对象字符的数量,而count 的类型为 int,因此,我们可以推测最长的长度为 2^32,也就是4G
   
不过,我们在编写源代码的时候,如果使用 Sting str ="aaaa";的形式定义一个字符串,那么双引号里面的ASCII字符最多只能有 65534 个。为什么呢?因为在class文件的规范中, CONSTANT_Utf8_info表中使用一个16位的无符号整数来记录字符串的长度的,最多能表示 65536个字节,而java class 文件是使用一种变体UTF-8格式来存放字符的,null值使用两个字节来表示,因此只剩下 65536 2 65534个字节。也正是变体UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么双引号中字符的数量会更少(一个中文字符占用三个字节)。如果超出这个数量,在编译的时候编译器会报错。

 

2JAVA对象的串行化能存储在静态变量中吗?

Java中对象的串行化(Serialization)和transient关键字

一、串行化的概念和目的

1.
什么是串行化
      
对象的寿命通常随着生成该对象的程序的终止而终止。有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复。我们把对象的这种能记录自己的状态以便将来再生的能力。叫作对象的持续性(persistence)。对象通过写出描述自己状态的数值来记录自己 ,这个过程叫对象的串行化(Serialization-连续) 。串行化的主要任务是写出对象实例变量的数值。如果变量是另一对象的引用,则引用的对象也要串行化。这个过程是递归的,串行化可能要涉及一个复杂树结构的单行化,包括原有对象、对象的对象、对象的对象的对象等等。对象所有权的层次结构称为图表(graph)

2.
串行化的目的

Java
对象的单行化的目标是为Java的运行环境提供一组特性,如下所示:

1)
尽量保持对象串行化的简单扼要 ,但要提供一种途径使其可根据开发者的要求进行扩展或定制。
2)
串行化机制应严格遵守Java的对象模型 。对象的串行化状态中应该存有所有的关于种类的安全特性的信息。
3)
对象的串行化机制应支持Java的对象持续性。
4)
对象的串行化机制应有足够的 可扩展能力以支持对象的远程方法调用(RMI)
5)
对象串行化应允许对象定义自身 的格式即其自身的数据流表示形式,可外部化接口来完成这项功能。



二、串行化方法
JDK1.1开始,Java语言提供了对象串行化机制 ,在java.io包中,接口Serialization用来作为实现对象串行化的工具 ,只有实现了Serialization的类的对象才可以被串行化。

Serializable
接口中没有任何的方法。当一个类声明要实现Serializable接口时,只是表明该类参加串行化协议,而不需要实现任何特殊的方法。下面我们通过实例介绍如何对对象进行串行化。

1.
定义一个可串行化对象

一个类,如果要使其对象可以被串行化,必须实现Serializable接口。我们定义一个类Student如下:
 class Studentimplements Serializable {
//1
首先定义了一个类Student,实现了Serializable接口
    int id;  
    String name; 
    int age; 
    transient String department; 
// Java
语言的关键字[保留字],用来表示一个域不是该对象串行化的一部分。
    public Student(int id, String name, int age, Stringdepartment) {
       this.id = id;
       this.name = name;
       this.age = age;
       this.department = department;
    }
}
2.
构造对象的输入/输出流

要串行化一个对象,必须与一定的对象输出/输入流联系起来,通过对象输出流将对象状态保存下来,再通过对象输入流将对象状态恢复。

java.io
包中,提供了ObjectInputStream ObjectOutputStream将数据流功能扩展至可读写对象。在ObjectInputStream 中用readObject()方法可以直接读取一个对象,ObjectOutputStream中用writeObject()方法可以直接将对象保存到 输出流中。

package com.gwssi.study.pkg3;
import java.io.*;
public class ObjectSer {
    public static void main(String args[]) throwsIOException,ClassNotFoundException{
       Student stu = new Student(101972040,"YaoMing", 27, "basketball");
       FileOutputStream fo = newFileOutputStream("data.ser");
       ObjectOutputStream so = newObjectOutputStream(fo);
       try {
//2
通过对象输出流的writeObject()方法将Student对象保存到文件 data.ser
      so.writeObject(stu);
           so.close();
       } catch (IOException e) {
          System.out.println(e);
       }
       stu = null;
       FileInputStream fi = newFileInputStream("data.ser");
       ObjectInputStream si = newObjectInputStream(fi);
       try {
//3
通过对家输入流的readObjcet()方法从文件data.ser中读出保存的Student对象
           stu =(Student)si.readObject();
           si.close();
       } catch (IOException ex) {
          System.out.println(ex);
       }
       //4
从运行结果可以看到,通过串行化机制,可以正确地保存和恢复对象的状态
       System.out.println("StudentInfo:");
       System.out.println("ID:" +stu.id);
       System.out.println("Name:" +stu.name);
       System.out.println("Age:" +stu.age);
       System.out.println("Dep:" +stu.department);

    }

}
  运行结果如下:


Student Info:
ID:101972040
Name:YaoMing
Age:27
Dep:null

在这个例子中,

1
首先定义了一个类Student,实现了Serializable接口

2
通过对象输出流的writeObject()方法将Student对象保存到文件 data.ser

3
通过对家输入流的readObjcet()方法从文件data.ser中读出保存的Student对象

4
从运行结果可以看到,通过串行化机制,可以正确地保存和恢复对象的状态

三、串行化的注意事项
1.
串行化能保存的元素

串行化只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且串行化保存的只是变量的值,对于变量的任何修饰符都不能保存。

2.transient
关键字

对于某些类型的对象,其状态是瞬时的,这样的对象是无法保存其状态的。例如一个Thread对象或一个FileInputStream对象 ,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。

另外 ,串行化可能涉及将对象存放到磁盘上或在网络上发达数据,这时候就会产生安全问题。因为数据位于Java运行环境之外,不在Java安全机制的控制之中。对于这些需要保密的字段,不应保存在永久介质中,或者不应简单地不加处理地保存下来 ,为了保证安全性。应该在这些字段前加上transient关键字。

 

3. 如果把System.exit(0)放在catch块中,finally块是否还会被运行?

这个就不用说了吧,当然不会执行了。如果catch换成别的,那就难说了。。。

 

4.      JavaSystem.gc()是否能够启动垃圾回收?

5.      finalize()是由JVM自动调用的,你可以用System.gc(),但JVM不一定会立刻执行,JVM感觉内存空间有限时,才会开始执行finalize(),至于新的对象创建个数和被收集个数不同是因为收集的对象只和JVM的垃圾收集策略有关。


1.
构造函数
要点:
建器(Constructor)属于一种较特殊的方法类型,因为它没有返回值.这与 void返回值存在着明显的区别。对于void返回值,尽管方法本身不会自动返回什么,但仍然可以让它返回另一些东西。构建器则不同,它不仅什么也不会自 动返回,而且根本不能有任何选择.若创建一个没有构件器的类,则编译器会自动创建一个默认构件器.

2.finalize()
gc()

(1)
问题:finalize()函数是干嘛的?Java不是有Garbage Collection(以下简称gc)来负责回收内存吗?
回答:
gc
只能清除在堆上分配的内存(java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内,例如java调用c程序,而该c程序使用malloc分配内存时).因此,如果某些对象被分配了栈上的内存区域,gc就管不着了,对这样的对象进行内存回收就要靠finalize().
举个例子来说,java 调用非java方法时(这种方法可能是c或是c++的),在非java代码内部也许调用了cmalloc()函数来分配内存,而且除非调用那个了 free() 否则不会释放内存(因为free()c的函数),这个时候要进行释放内存的工作,gc是不起作用的,因而需要在finalize()内部的一个固有方法 调用它(free()).
finalize
的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.

(2)
问题:finalize()在什么时候被调用?
回答:

有三种情况

1.
所有对象被GarbageCollection时自动调用,比如运行System.gc()的时候.
2.
程序退出时为每个对象调用一次finalize方法。
3.
显式的调用finalize方法

除此以外,正常情况下,当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,但是jvm不保证finalize()一定被调用,也就是说,finalize()的调用是不确定的,这也就是为什么sun不提倡使用finalize()的原因.


3. this
要点:
this
关键字只能在方法中使用,它能为调用该方法的对象提供相应的句柄,使得同一个类产生的不同对象实例在调用同一方法的时候,系统能判断出是哪一个对象在进行调用.
比如:
MyObject a=new MyObject();
MyObject b=new MyObject();
a.f();// (3)
b.f();// (4)
编译器在编译的时候,实际上是将(3),(4)句解释为
MyObject.f(a);
MyObject.f(b);
,这样就将调用了该方法的对象的信息传到了方法中,也就是传给了this,就可以通过this表示调用该方法的对象实例.

this的概念还可以解释为什么在静态方法中不能调用非静态方法和元素,这是因为静态方法中没有this,也就是说我们不能获得调用该方法的对象的句柄.既然找不到这个对象实例,我们又怎么能够在其中调用对象实例的方法和元素呢?

为什么静态方法没有this?用静态方法的概念可以来理解这个问题.静态方法是类方法,是所有对象实例公用的方法.它不属于某一个具体的对象实例,因此 也无法用this来体现这个实例.这和非静态方法是不一样的.打个比方,在一个局域网内的几个用户每个人都有一台客户机,但都访问一台公共的服务器.对于 每台客户机来说,它的this就是使用它的用户.而对于服务器来说,它没有this,因为它是大家公用的,不针对某一个具体的客户.

4.
对象初始化
要点:
1.
对象只有在创建的时候,需要使用它的时候才进行初始化,否则永远都不会初始化.
2.
对象进行初始化是有一定顺序的,无论在定义的时候各个成员的摆放位置如何.首先是静态成员和对象,然后是非静态成员和对象,最后才运行构造器.
3.
静态成员和对象有且只有一次初始化过程,这个过程发生在第一次创建对象或者第一次使用类的静态成员和对象的时候.

以一个名为Dog的类为例,它的对象实例初始化过程如下:
(1)
类型为Dog的一个对象首次创建时,或者Dog类的static方法/static字段首次访问时,Java解释器必须找到Dog.class(在事先设好的类路径里搜索)。
(2)
找到Dog.class,它的所有static初始化模块都会运行。因此,static初始化仅发生一次?D?DClass对象首次载入的时候。
(3)
创建一个new Dog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间。
(4)
这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值
(5)
进行字段定义时发生的所有初始化都会执行。
(6)
执行构建器。正如第6章将要讲到的那样,这实际可能要求进行相当多的操作,特别是在涉及继承的时候

5.
数组的初始化
数组包括基本数据类型数组和对象数组,其中对于对象数组的初始化,经常会出现"Exception"错误.比如下面的程序

问题代码如下:

public userInfo[] getUsersInfo() {

userInfo[] usersInfo=null;

if (users.size()!=0) {
usersInfo=new userInfo[users.size()];

for(int i=0;i< usersInfo.length;i++) {
//+-------------------
出问题的地方-----------------
usersInfo[i].name=((User)(users.elementAt(i))).name;
usersInfo[i].type=((User)(users.elementAt(i))).type;
usersInfo[i].userID=((User)(users.elementAt(i))).userID;
//+-------------------
出问题的地方-----------------
}
System.out.println("here");
return usersInfo;
}else {
return null;
}
}



其中userInfo的定义为

class userInfo{
userInfo(String name,int type,int userID){
this.name=name;
this.type=type;
this.userID=userID;
}
String name;
int type;
int userID;
}



运行到程序中标出的问题区域时,系统显示NullPointerException,为什么会这样呢?

这是因为,Java在定义数组的时候
usersInfo=new userInfo[users.size()];
并没有给数组元素分配内存,它只是一个句柄数组,数组中的对象还没有初始化.因此数组中的每个对象都需要new之后才可以访问.例如:
A[] a=new A[2];
for(int i=0;i<2;i++)
a[i] = new A();
这样才能a[i].someMethod()

因此上面的程序应该改为

public userInfo[] getUsersInfo() {

userInfo[] usersInfo=null;

if (users.size()!=0) {
usersInfo=new userInfo[users.size()];

for(int i=0;i< usersInfo.length;i++) {
//+-------------------
修改的地方-----------------
usersInfo[i]=new userInfo(((User)(users.elementAt(i))).name,
((User)(users.elementAt(i))).type,
((User)(users.elementAt(i))).userID);
}
//+-------------------
修改的地方-----------------
return usersInfo;
}else {
return null;
}
}


没问题了简单来讲,finalize()是在对象被GC回收前会调用的方法,而System.gc()强制GC开始回收工作纠正,不是强制,是建议,具体执行要看GC的意思简单地说,调用了 System.gc() 之后,java 在内存回收过程中就会调用那些要被回收的对象的 finalize() 方法。

 

5: 请说出JNI的很多缺点中的一个缺点?

使用比较麻烦,需要对已有的DLL进行封装,需要对C/C++比较了解;

破坏了程序的可移植性;

影响程序的安全性

。。。

 

6Java里的Annotation(注释),能被子类继承吗?

有关Annotation的继承说明:

1JDK文档中的说明是:只有在类上应用的Annotation才能被继承,而实际应用时的结果是:除了类上应用的Annotation能被继承外,没有被重写的方法的Annotation也能被继承。

2、要注意的是:当方法被重写后,Annotation将不会被继承

3、要使得Annotation 被继承,需要在Annotation中加标识@Inherited,并且如果要被反射应用的话,就需要还有个@Retention(RetentionPolicy.RUNTIME)标识

4Annotation的继承不能应用在接口上

。。。

 

之后我们还聊了很多,他在美国研发的现状,受金融风暴的影响等等。。。

 

几个看似普通的题目,几分钟的对话,让我对这个和自己年龄相仿的sun程序员刮目相看,从中看到了人家做事的态度、学习的习惯和超脱的能力。。。

 

以后在开发的过程中也好,做人的生活中也好,给自己提个醒钟,多注意身边的细节。。。

 

原创粉丝点击