【02】【Java常见面试题】

1、什么是java序列化,如何实现java序列化? 

        Java 串行化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。这种机制允许你将对象通过网络进行传播,并可以随时把对象持久化到数据库、文件等系统里。Java的串行化机制是RMI、EJB等技术的技术基础。用途:利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。

        序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

        序列化的实现:将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

        串行化的特点:

        1、如果某个类能够被串行化,其子类也可以被串行化。如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可串行化接口。则其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可串行化接口,则该类的父类所有的字段属性将不会串行化。

        2、声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;

        3、相关的类和接口:在java.io包中提供的涉及对象的串行化的类与接口有ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、ObjectInputStream类。

         ObjectOutput接口:它继承DataOutput接口并且支持对象的串行化,其内的writeObject()方法实现存储一个对象。

        ObjectInput接口:它继承DataInput接口并且支持对象的串行化,其内的readObject()方法实现读取一个对象。

        ObjectOutputStream类:它继承OutputStream类并且实现ObjectOutput接口。利用该类来实现将对象存储(调用ObjectOutput接口中的writeObject()方法)。

        ObjectInputStream类:它继承InputStream类并且实现ObjectInput接口。利用该类来实现读取一个对象(调用ObjectInput接口中的readObject()方法)。

        4、对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

用法:

import java.io.*;

public class Cat implements Serializable {
    private String name;
	
    public Cat () {
        this.name = "new cat";
    }
	
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {          
        Cat cat = new Cat();
        try {
            FileOutputStream fos = new FileOutputStream("catDemo.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            System.out.println(" 1> " + cat.getName());
            cat.setName("My Cat");                        
            oos.writeObject(cat);
            oos.close();                        
        } catch (Exception ex) {  
			x.printStackTrace();  
		}
	
        try { 

            FileInputStream fis = new FileInputStream("catDemo.out");
            ObjectInputStream ois = new ObjectInputStream(fis);
            cat = (Cat) ois.readObject();
            System.out.println(" 2> " + cat.getName());
            ois.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}//writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来

2、反射机制 

        反射机制最重要的部分是准许你检查类的结构。java.lang.reflect包中的三个类Field、Method、Constructor相应的描述了一个类的字段、方法、构造函数。使用这些类的时候必须要遵循下面步骤:

        第一步是获得你想操作的类的 java.lang.Class 对象。下面就是获得一个 Class 对象的方法之一:Class c = Class.forName("java.lang.String"); //这条语句得到一个 String 类的类对象。还有另一种方法:Class c = int.class; 或者Class c = Integer.TYPE; //可获得基本类型的类信息。 

        第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。 

        Method m[] = c.getDeclaredMethods(); 

        System.out.println(m[0].toString()); //以文本方式打印出 String 中定义的第一个方法的原型。 

        Class.forName 的作用 ? 为什么要用 ?

        答: Class是运行中的class类,forName(className)是将这个名为className的类装入JVM,这样就可以动态的加载类,通过Class的反射机制可以获得此类的一些信息。Class.forName 的作用动态加载和创建Class 对象。

        类加载器是用来加载.class文件,读取.class文件的字节码并加载到内存中。

        关于类的初始化(执行static程序段):

        1、A a = new A();//在类加载的时候即进行初始化

        2、Class.forName(A);//在类加载(载入class)的时候即进行初始化

        3、Class.forName(A,false,classLoader);//在newInstance的时候进行初始化

        4、classLoader.loadClass(A);//在newInstance的时候进行初始化

        static块仅执行一次

        (1) 使用Class.forName()

        public static Class forName(String className)

        public static Class forName(String className, boolean initialize,ClassLoader loader)

        参数说明:

        className - 所需类的完全限定名(包括全路径)

        initialize - 是否必须初始化类(静态代码块的初始化)

        loader - 用于加载类的类加载器 

        不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。

 3、Class.forName 的作用,为什么要用 。

        Class是运行中的class类,forName(className)是将这个名为className的类装入JVM,这样就可以动态的加载类,通过Class的反射机制可以获得此类的一些信息。Class.forName 的作用动态加载和创建Class 对象。

类加载器是用来加载.class文件,读取.class文件的字节码并加载到内存中。

        关于类的初始化(执行static程序段)

        1、A a = new A();//在类加载的时候即进行初始化

        2、Class.forName(A);//在类加载(载入class)的时候即进行初始化

        3、Class.forName(A,false,classLoader);//在newInstance的时候进行初始化

        4、classLoader.loadClass(A);//在newInstance的时候进行初始化

        static块仅执行一次

        (1) 使用Class.forName()

        public static Class forName(String className)

        public static Class forName(String className, boolean initialize,ClassLoader loader)

        参数说明:

        className - 所需类的完全限定名(包括全路径)

        initialize - 是否必须初始化类(静态代码块的初始化)

        loader - 用于加载类的类加载器

        不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。

        Class类和对象

        类是程序的一部分,每个类都有一个Class对象。换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当地说,是保存在一个完全同名的.class文件中)。在运行期,一旦我们想生成这个类的对象,运行这个程序的Java虚拟机(JVM)首先就会检查这个类的Class对象是否已经载入。若尚未载入,JVM就会根据类名查找.class文件,并将其载入。所以Java程序并不是一开始就被完全加载的,这一点与许多传统语言都不同。

        一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。注意:Class对象仅在需要的时候才被加载。Class类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

        基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

        有三种方法可以获取Class的对象:

        1、调用Object类的getClass()方法来得到Class对象。例如:

        MyObject x;    Class c1 = x.getClass();

        2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:

        Class c2=Class.forName("MyObject");//MyObject必须是接口或者类的名字。

        3、如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如

        Class cl1 = Manager.class;    Class cl2 = int.class;    Class cl3 = Double[].class;

        注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。

        Class类的常用方法

        1、getName()   以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

        2、newInstance()   为类创建一个实例。例如: x.getClass.newInstance()。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。

        3、getClassLoader()     返回该类的类加载器。

        4、getSuperclass()  返回表示此 Class 所表示的实体的超类的 Class。

        5、isArray()   判定此 Class 对象是否表示一个数组类。

        Class的一些使用技巧

        1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象。例如    Object obj = Class.forName(s).newInstance();

        2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象。例如:    if(e.getClass() == Employee.class)...   

 4、java classLoader原理

        Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。类装载器是用来把类 (class) 装载进 JVM 的。JVM 规范定义了两种类型的类装载器:启动内装载器 (bootstrap) 和用户自定义装载器。
        bootstrap 是 JVM 自带的类装载器,用来装载核心类库,如 java.lang.* 。java.lang.Object 是由 bootstrap 装载的。Java 提供了抽象类 ClassLoader ,所有用户自定义类装载器都实例化自 ClassLoader 的子类。

        System Class Loader 是一个特殊的用户自定义类装载器,由 JVM 的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过 ClassLoader.getSystemClassLoader() 方法得到。
        类装载器把一个类装入 Java 虚拟机中,要经过三个步骤:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
        1、装载:寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的 class 对象的过程。

        2、链接:就是把class类型数据合并到JVM得运行时状态中去。执行校验、准备和解析步骤;

        校验:检查导入类或接口的二进制数据的正确性; 准备:给类的静态变量分配并初始化存储空间; 解析:将符号引用转成直接引用;

        3、初始化:初始化 Java 代码和静态 Java 代码块。初始化在JVM第一次主动使用该类型时进行的。所谓主动使用包括以下几种情况:
        1)创建类的新实例时(new指令或通过不明确的创建,反射,克隆或反序列化)
        2) 调用类的静态方法时
        3)使用类的静态字段,或对该字段赋值时(final修饰的静态字段除外)
        4)初始化某个类的子类时
        5) JVM启动时某个被标明为启动类的类即含有main()方法的类

        数组类的 Class 对象不是由类装载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类装载器由 Class.getClassLoader()返回,该装载器与其元素类型的类装载器是相同的;如果该元素类型是基本类型,则该数组类没有类装载器。

        虚拟机加载类的途径:

        1、Dog dog = new Dog();

        2、Class clazz = Class.forName(“Dog”);

        Object dog =clazz.newInstance();

        3、Class clazz = classLoader.loadClass(“Dog”);

        Object dog =clazz.newInstance();

        那么,1和2和3究竟有什么区别呢?分别用于什么情况呢?

        1和2使用的类加载器是相同的,都是当前类加载器。(即:this.getClass.getClassLoader)。

        3由用户指定类加载器。如果需要在当前类路径以外寻找类,则只能采用第3种方式。第3种方式加载的类与当前类分属不同的命名空间。

        另外,第1种和第2种都会导致执行类的静态初始化语句,而第3种情况不会。另外第1种抛出Error,第2、3种抛出Exception,它们分属于不同的异常/错误分支。

5、对象创建过程/初始化顺序 

        Java虚拟机创建一个对象都包含以下步骤。

        1、给对象分配内存。

        2、将对象的实例变量自动初始化为其变量类型的默认值。

        3、初始化对象,给实例变量赋予正确的初始值。

        对于以上第三个步骤,Java虚拟机可采用3种方式来初始化对象,到底采用何种初始化方式取决于创建对象的方式。

        1、如果对象是通过clone()方法创建的,那么Java虚拟机把原来被克隆对象的实例变量的值拷贝到新对象中

        2、如果对象是通过ObjectInputStream类的readObject()方法创建的,那么Java虚拟机通过从输入流中读入的序列化数据来初始化那些非暂时性(non-transient)的实例变量。

        3、在其他情况下,如果实例变量在声明时被显式初始化,那么就把初始化值赋给实例变量,接着再执行构造方法。这是最常见的初始化对象的方式。

 

        初始化的顺序是:先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是非静态对象。在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

        对象创建的过程:

        1、首次创建对象时,类中的静态方法/静态字段首次被访问时,java解释器必须先查找类路径,以定位.class文件;

        2、然后载入.class(这将创建一个class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。

        3、当用new XX()创建对象时,首先在堆上为对象分配足够的存储空间。

        4、这块存储空间会被清0,这就自动地将对象中的所有基本类型数据都设置成了缺省值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了null。

        5、执行所有出现于字段定义处的初始化动作(非静态对象的初始化)。

        6、执行构造器。

        对象的初始化

        1、非静态对象的初始化 

        在创建对象时,对象所在类的所有数据成员会首先进行初始化。基本类型:int型,初始化为0。如果为对象:这些对象会按顺序初始化。 

        ※在所有类成员初始化完成之后,才调用本类的构造方法创建对象。构造方法的作用就是初始化。 

        3、静态对象的初始化 

        程序中主类的静态变量会在main方法执行前初始化。不仅第一次创建对象时,类中的所有静态变量都初始化,并且第一次访问某类(注意此时未创建此类对象)的静态对象时,所有的静态变量也要按它们在类中的顺序初始化。 

        继承时,对象的初始化过程 

        1、主类的超类由高到低按顺序初始化静态成员,无论静态成员是否为private。 

        2、主类静态成员的初始化。 

        3、主类的超类由高到低进行默认构造方法的调用。注意,在调用每一个超类的默认构造方法前,先进行对此超类进行非静态成员的初始化。  

        4、主类非静态成员的初始化。 

        5、调用主类的构造方法。  

6、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? 

        对于GC来说,当创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。强制执行垃圾回收:System.gc()。Runtime.getRuntime().gc()

        静态类:static的是属于类的,而不是属于对象的,相当于是全局的,不可能被回收

        静态变量本身不会被回收,但是它所引用的对象应该是可以回收的。

        gc只回收heap里的对象,对象都是一样的,只要没有对它的引用,就可以被回收(但是不一定被回收). 对象的回收和是否static没有什么关系!

        如:static Vector pane = new Vector();  pane = null;  如果没有其它引用的话,原来pane指向的对象实例就会被回收。

        Java程序员在编写程序的时候不再需要考虑内存管 理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

6、java中会存在内存泄漏吗,请简单描述。 

        会。

        java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

        1、集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收。而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。

        2、单例模式。不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露

      编程建议

        在代码复审的时候关注长生命周期对象:全局性的集合、单例模式的使用、类的static变量等等。在Java的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋空。最好遵循谁创建谁释放的原则。




【向原作者们至敬】

【参见】【http://blog.csdn.net/yakihappy/article/details/3979373】

【参见】【http://blog.csdn.net/yakihappy/article/details/3979369

【参见】【http://blog.csdn.net/yakihappy/article/details/3979357】

【参见】【http://blog.csdn.net/yakihappy/article/details/3979940】

你可能感兴趣的:(java,面试)