java面试题目查漏补缺

128 阅读7分钟

== & equals

==是java中的关系运算符:

  • 基本类型: 比较栈中的值是否相同
  • 引用类型: 比较栈中的地址是否相同

equals()是Object类的方法, 在代码中用==运算

public boolean equals(Object obj) {
	return (this == obj);
}

String和Integer包装类型重写了equals()方法, 故这两个类调用equals比较的是两个对象的值是否相同

StringBuilder和StringBuffer没有对equals重写, equals()==的比较逻辑相同, 都是比较地址

throw & throws

throw表示程序中明确的抛出异常, 后跟异常对象, 位于方法体中

throws表明程序不能处理的异常, 抛给上一层处理, 写在方法声明后

    public static void function1(String str) throws NumberFormatException {
        int num;
        try {
            num = Integer.valueOf(str);
        } catch NumberFormatException e {
            throw new NumberFormatException();
        }
        ...
    }

java序列化

Serializable标记性接口, 不含属性或方法

父类实现了Serializable接口, 则子类也可序列化, 不必再实现接口

static属性属于类, 不会被序列化

Serializable是深拷贝, 其引用的对象也会被序列化(但对象或对象父类需实现Serializable接口, 否则报NotSerializableException错)

transient关键字标记的字段可以在序列化时被忽略

如果对类对象被序列化时的操作有更独特的要求, 可以在类中实现private的writeObject(stream)readObject(stream)方法, 在调用ObjectIn/OutputStream的writeObject()或readObject方法时, 会跳过正常序列化过程,用反射机制调用对象的私有对应方法.

    Method method = object.getClass().getDeclaredMethod("writeObject");
    method.setAccessability(true);
    method.invoke(object);

为什么需要是private方法: 防止Override接口中的方法, 接口方法均为public(默认 public static final)

接口 vs 抽象类

接口抽象类
抽象方法√(可以无)√(抽象类中可以无抽象方法, 但有抽象方法的类只能是抽象类)
非抽象方法×
静态方法× JDK8后√(默认public static final)√(可以为任意可见性)
default方法× JDK8后√
构造方法×
静态成员变量
普通成员变量×
类final××
创建实例××(不能直接创建实例, 但子类继承后实例化时会先实例化父类. 构造方法用于初始化对象成员变量)
类实现数量
类实现方法必须全部可以部分

字节流 vs 字符流

字节流(InputStream, OutputStream接口的实现类)操作字节, read()返回int范围0-255

字符流(Reader, Writer接口的实现类)操作字符, read()返回int范围0-65535

字节流在操作时本身不会用到缓冲区(内存), 与文件本身直接操作的, 因此不用close()资源就可以读数据

而字符流在操作时用到缓冲区, 因此可以用flush()将数据从缓冲区中送走, 才能读到数据, 否则需要close()资源才能读到

标准I/O

程序中的"单一信息流", 可以方便地将程序串联起来, 一个程序的标准输出可以作为另一个程序的标准输入

java的标准I/O提供了System.in, System.out, System.err, 均使用PrintStream

还可以用System的setIn, setOut, setErr将标准I/O重定向

内部类

普通内部类

普通内部类需要在外部类被实例化后才能实例化, 隐含地保存了一个指向其外部类的引用, 可以直接访问该外部类对象的所有方法和属性. 外部类访问内部类则需要通过对象名访问. 普通内部类可以是任意访问域(private也可)

java编译器在编译内部类时, 会默认为成员内部类添加了一个指向外部类对象的引用, 并为构造器默认添加指向外部类对象引用的参数

普通内部类不能有静态成员!

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。不能有public, private, protected, static等修饰符

匿名内部类

使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用

    new OnClickListener() {
             
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
             
        }
    }

局部内部类和匿名内部类只能访问局部final变量! 局部内部类和匿名内部类的使用的局部变量值, 如果在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝; 如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值

因此, 局部内部类和匿名内部类对局部变量的修改不能传回调用函数, 因此, 编译器限制其参数必须为final

静态内部类

静态内部类为类内部的static class, 不与外部类的对象相关联, 因此不能访问外部类对象的非静态方法和变量, 只能访问其静态成员.

静态内部类可以不依赖与外部类的实例化而实例化. 静态内部类可以有非静态成员, 如果访问静态内部类的非静态成员, 则需要将静态内部类实例化.

动态代理

/**
 * 注意需实现Handler接口  
 */
public class LoggerInterceptor implements InvocationHandler {
    private Object target;//目标对象的引用,这里设计成Object类型,更具通用性  
    public LoggerInterceptor(Object target){  
        this.target = target;  
    }
    
    public Object invoke(Object proxy, Method method, Object[] arg)  throws Throwable {  
        System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");  
        Object result = method.invoke(target, arg);//调用目标对象的方法  
        System.out.println("Before return:"+result);  
        return result;  
    }  
}  

/**
 * 动态生成代理类
 */
public class Main {  
    public static void main(String[] args) {  
        AppService target = new AppServiceImpl();//生成目标对象  
        //接下来创建代理对象  
        AppService proxy = (AppService) Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), new LoggerInterceptor(target));  
        proxy.createApp("Kevin Test");  
    }  
}  

对象拷贝

Cloneable

标记接口, 但需要自己实现clone()方法, 否则只是复制引用地址

Serializable

序列化, 同样是标记接口. 不需要实现任何方法

ObjectInputstream writeObject方法序列化

ObjectOutputstream readObject方法反序列化

如果含有引用属性变量, 同样需要实现Serializable接口, 否则报异常

如果有不想序列化的字段, 可以用transient标记

Externalizable

如果想要定制实现对象被序列化的结构, 可以实现Externalizable接口, 添加private(不会覆盖接口的public方法)的writeObject()和readObject()方法.

类的初始化

类的初始化顺序:

  • 父类静态成员,静态代码块
  • 子类静态成员,静态代码块
  • 父类普通成员普通代码块
  • 父类构造方法
  • 子类普通成员普通代码块
  • 子类构造方法

如果子类只是用来调用父类的静态成员对象, 如Child.NUMBER, 则仅父类会被初始化.

如果仅访问子类的final静态成员变量, 则子类也不会初始化(编译器放到常量池)

创建一个类的数组对象, 并不会导致该类的初始化. 因为JVM会自动生成一个直接继承于Object类的子类, 直接创建一个newarray, 即为实现了原本类的方法和属性的类(但为私有, 用户不能直接访问), 这个类会控制数组越界, 抛出ArrayIndexOutOfBoundsException, 而不是直接非法内存访问.

ArrayList扩容

默认初始容量为10, 每次扩容至:

int newCapacity = oldCapacity + (oldCapacity >> 1);

finally执行时间

public class Main {
    public static void main(String[] args) {
        System.out.print(fun1());
    }
    public static String fun1() {
        try {
            System.out.print("A");
            return fun2();
        } finally {
            System.out.print("B");
        }
    }
    public static String fun2() {
        System.out.print("C");
        return "D";
    }
}

输出为: ACBD

JVM类生命周期

加载

通过类的全限定名获取该类的文件字节流

将字节流的静态救过转换为方法区的运行时数据结构

生成java.lang.Class对象, 作为方法区中这个类的访问入口

连接

又分为:

验证

验证文件格式, 元数据, 字节码(程序语义), 符号引用(可访问)

准备

为类变量分配内存, 设置初始值(0)

解析

将常量池中的符号引用(全限定名)替换为直接引用

初始化

(先执行父类的<clinit>)

执行类构造器<clinit>

初始化静态变量, 执行静态代码块(仅这里执行一次)

使用

卸载

JVM类加载器

最父类的加载器是启动类加载器(Bootstrap ClassLoader)

双亲委派模型

破坏双亲委派模型(jdk1.2才出现双亲委派模型, 可能以前的代码写法不必用亲加载)

JVM判断java类是否相同

  1. 全限定名相同
  2. ClassLoader相同

JDK指令

jps java ps jstat java运行时堆信息 jmap 堆内存 jstack 堆栈快照 jconsole 监控java应用程序性能和代码