Java面试Java Core面试

Java&Android 基础知识梳理(0) - Jav

2018-12-26  本文已影响82人  泽毛

Java 基础知识大纲

一、面向对象

1.1 对 Java 多态的理解

面向对象编程的三大特性:封装、继承、多态。

实现多态的三个必要条件:继承、重写、向上转型。

实现形式:

1.2 父类静态方法能不能被子类重写

结论

父类的静态方法可以被子类继承,但是不能被子类重写。

当子类声明了一个与父类相同的静态方法时,只能称为隐藏。

/**
 * @author lizejun
 **/
public class Parent {
    
    public static void staticMethod() {
        System.out.println("Parent Static Method");
    }
    
    public void method() {
        System.out.println("Parent Method");
    }
}
/**
 * @author lizejun
 **/
public class Parent {

    public static void staticMethod() {
        System.out.println("Parent Static Method");
    }

    public void method() {
        System.out.println("Parent Method");
    }
}
/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        Parent parent = new Child();
        parent.method();
        parent.staticMethod();
        
        Child child = new Child();
        child.method();
        child.staticMethod();
    }
}
Child Method
Parent Static Method
Child Method
Child Static Method

二、Object 类相关

2.1 Java 中 ==、equals 和 hashCode 的区别

==

Java中,分为基本数据类型和复合数据类型,基本数据类型包括byteshortcharintlongfloatdoubleboolean这八种。

equals

Objectequals默认的实现是比较两个对象是不是==,和==的效果是相同的。

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

而有些时候,对于两个不同的对象,我们又需要提供 逻辑 上是否相等的判断方法,这时候就需要重写equals方法。Java提供的某些类已经重写了equals方法,用于判断"相等"的逻辑,例如Integer

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

hashCode

hashCode的目的是用于在对象进行散列的时候作为key输入,保证散列的存取性能。Object的默认hashCode实现为在对象的内存地址上经过特点的算法计算出。
由此可见,equalshashCode的其实没有什么关系。但是由于HashSet/HashMap容器的存在,又需要保证:

Java&Android 基础知识梳理(8) - 容器类 中提到的HashMap的实现,value替换的条件是判断key

//Value 替换的条件
//条件1:hash 值完全相同
//条件2:key 指向同一块内存地址 或者 key 的 equals 方法返回为 true
(e.hash == hash && ((k = e.key) == key || key.equals(k)))

假如我们只重写了equals方法,而没有重写hashCode方法,就会导致逻辑上相等的两个key,放在了容器中的不同位置。

2.2 Integer

存储原理

缺省值

泛型支持

泛型支持Integer,不支持int

int 与 Integer 之间的比较

//基本数据类型。
int a1 = 128;
//非 new 出来的 Integer。
Integer a2 = 128;
//new 出来的 Integer。
Integer a3 = new Integer(128);
    public static Integer valueOf(int i) {
        //low = -128, high = 127.
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

2.3 String

2.3.1 new String 和直接赋值的区别

new String和直接赋值的区别:

例子 1

String s ="a" + "b" + "c" + "d"

只创建了一个对象,在编译器在编译时优化后,相当于直接定义了一个abcd的字符串。

例子 2

String ab = "ab";                                          
String cd = "cd";                                       
String abcd = ab + cd;                                      
String s = "abcd";  

abcd存储的是两个常量池中的对象,当执行ab + cd时,首先会在堆中创建一个StringBuilder类,同时用ab指向的字符串对象完成初始化,然后调用append方法完成对cd指向字符串的合并操作,接着调用StringBuildertoString方法在堆中创建一个String对象,最后将刚生成的String对象的地址存放在局部变量abcd中。

2.3.2 String、StringBuilder、StringBuffer 的区别

对比

注意点

2.3.3 String 为什么要设计成不可变类

常量池的需要

字符串常量池是Java堆内存的一个特殊区域,当创建一个String对象时,假如字符串已经存在于常量池中,则不会创建新的对象,而是直接引用已经存在的对象。

String s1 = "abc";
String s2 = "abc";

s1s2指向常量池中的同一个对象abc,如果String是可变类,s1对其的修改将会影响到s2

HashCode 缓存的需要

因为字符串不可变,在创建的时候HashCode就被缓存,不需要重新计算。

多线程安全

由多个线程之间共享,不需要同步处理。

如何实现不可变

2.4 序列化 & 反序列化

Java&Android 基础知识梳理(2) - 序列化

三、重要关键字

3.1 final

final可以用于以下四个地方:

3.1.1 变量

静态变量

非静态变量

final修饰的变量必须被初始化,初始化的方式有以下几种:

3.1.2 方法

不可以被子类重写,但是不影响被子类继承。

3.1.3 类

不允许被继承。

3.2 static

四、内部类

4.1 定义

内部类的定义:在一个外部类的内部再定义一个类。

4.2 分类

4.3 作用

4.3.1 实现隐藏

外部顶级类即类名和文件名相同的只能使用publicdefault修饰,但内部类可以是staticpublic/default/protected/private

首先,定义内部类需要实现的接口:

/**
 * @author lizejun
 **/
public interface InnerInterface {
    void call();
}

再定义一个包装类:

/**
 * @author lizejun
 **/
public class Outer {

    private class InnerImpl implements InnerInterface {

        @Override
        public void call() {
            System.out.println("call inner");
        }
    }

    public InnerInterface getInnerInterface() {
        return new InnerImpl();
    }
}

由于我们将InnerInterface的实现类声明为了private,因此外部并不知道它的存在,也就达到了隐藏的目的。

/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        Outer outer = new Outer();
        InnerInterface inner = outer.getInnerInterface();
        inner.call();
    }
}

4.3.2 无条件地访问外部类当中的元素

/**
 * @author lizejun
 **/
public class Outer {
    
    //外部类的私有变量。
    private int outerSelfValue = 0;

    private class InnerImpl implements InnerInterface {

        @Override
        public void call() {
            //内部类可以无条件地访问。
            System.out.println("call inner, outerValue=" + outerSelfValue);
        }

    }

    public InnerInterface getInnerInterface() {
        return new InnerImpl();
    }
}

这仅限于非静态内部类,它和静态内部类的区别是:

4.3.3 实现多重继承

由于Java不允许多重继承,因此假如我们希望一个类同时具备其它两个类的功能时,就可以采用内部类来实现。

实现乘法的子类:

/**
 * @author lizejun
 **/
public class MultiCalculator {

    public int multi(int a, int b) {
        return a * b;
    }
}

实现加法的子类:

/**
 * @author lizejun
 **/
public class PlusCalculator {

    public int add(int a, int b) {
        return a;
    }
}
/**
 * @author lizejun
 **/
public class Calculator extends PlusCalculator {

    class MultiCalculatorImpl extends MultiCalculator {

        @Override
        public int multi(int a, int b) {
            return super.multi(a, b);
        }
    }

    public int multi(int a, int b) {
        return new MultiCalculatorImpl().multi(a, b);
    }
}

4.3.4 避免修改接口而实现同一个类中两种同名方法的调用

用于解决下面的困境:一个需要继承另一个类,还要实现一个接口,而继承的类和接口里面有两个同名的方法。那么我们调用该方法的时候,究竟是父类的,还是实现的接口呢,这时候就可以使用内部类来解决这一问题。

/**
 * @author lizejun
 **/
public class BaseOuter {

    public void call() {
        System.out.println("call baseOuter");
    }
}
/**
 * @author lizejun
 **/
public interface InnerInterface {
    void call();
}
/**
 * @author lizejun
 **/
public class Outer extends BaseOuter {

    private class InnerImpl implements InnerInterface {

        @Override
        public void call() {
            //内部类可以无条件地访问。
            System.out.println("call inner");
        }

    }

    public InnerInterface getInnerInterface() {
        return new InnerImpl();
    }
}
/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        Outer outer = new Outer();
        //1. 调用的是继承父类的接口。
        outer.call();
        //2. 调用的实现接口的方法。
        outer.getInnerInterface().call();
    }
}

4.4 应用场景

幕后英雄的用武之地——浅谈 Java 内部类的四个应用场景

/**
 * @author lizejun
 **/
public interface InnerWorker {
    void work();
}
/**
 * @author lizejun
 **/
public class Factory {

    public void doWork(InnerWorker worker) {
        try {
            worker.work();
        } catch (Exception exception) {
            System.out.println("exception!");
        } finally {
            System.out.println("finally!");
        }
    }
}
/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        Factory factory = new Factory();
        factory.doWork(new InnerWorker() {

            @Override
            public void work() {
                System.out.println("work1 work");
            }
        });
        factory.doWork(new InnerWorker() {

            @Override
            public void work() {
                System.out.println("work2 work");
            }
        });
    }
}
/**
 * @author lizejun
 **/
public interface Shape {
    void draw();
}
/**
 * @author lizejun
 **/
public abstract class ShapeFactory {

    private static HashMap<String, ShapeFactory> factories = new HashMap();

    public static void addFactory(String id, ShapeFactory factory) {
        factories.put(id, factory);
    }

    public static Shape createShape(String id) {
        if (!factories.containsKey(id)) {
            try {
                Class.forName(id);
            } catch (Exception e) {}
        }
        return factories.get(id).create();
    }

    protected abstract Shape create();

}
/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        Shape shape = ShapeFactory.createShape(Circle.ID);
        shape.draw();
    }
}

4.5 内部类和闭包

闭包就是把函数以及变量包起来,使得变量的生存周期延长,闭包跟面向对象是一棵树上的两条枝,实现的功能是等价的。

涉及到闭包的两种内部是:局部内部类和匿名内部类。当它们引用外部变量时,外部的变量需要是final的。

以下面这个例子为例,定义一个内部类的接口:

/**
 * @author lizejun
 **/
public interface InnerInterface {
    void call();
}
/**
 * @author lizejun
 **/
public class InnerClose {
    
    public void doClose(final int a) {
        InnerInterface inner = new InnerInterface() {
            
            @Override
            public void call() {
                System.out.println("a=" + a);    
            }
        };
        inner.call();
    }
}
/**
 * @author lizejun
 */
public class MainApp {

    public static void main(String[] args) {
        InnerClose close = new InnerClose();
        close.doClose(1);
    }
}

在编译之后,局部内部类会生成独立的InnerClose$1.class文件,而变量a是方法级别的,方法运行完变量就销毁了,而局部内部类对象还可能一直存在,不会随着方法运行结束就马上被销毁。这时候就会出现,局部内部类对象需要访问一个已经不存在的局部变量a

因此,通过将变量声明为final,编译器会将final局部变量复制一份,复制品作为局部内部类中的成员,这样,当局部内部类访问局部变量时,其实真正访问的是这个局部变量的复制品。

由于被final修饰的变量赋值后不能再修改,所以就保证了复制品与原始变量的一致,就好像是局部变量的 生命期变长了,这就是Java的闭包。

五、抽象类 & 接口

5.1 区别

5.2 应用场景

抽象类

在既需要统一的接口,又需要实例变量或缺省方法的情况下,可以使用:

接口

六、编码

6.1 为什么要编码

6.2 编码方式

ASCII 码

ASCII码总共有128个,用一个字节的低7位表示。

ISO-8859-1

ASCII码基础上制定了一系列标准来扩展ASCII编码,其仍然是单字节编码,总共能表示256个字符。

GB2312

双字节编码,总的范围是A1~F7,从A1~A9是符号区,总共包含682个符号;从B0~F7是汉字区,包含6763个汉字。

GBK

扩展GB2312,加入更多的汉字,其编码范围是8140~FEFE,和GB2312兼容。

GB18030

我国的强制标准,可能是单字节、双字节或者四字节编码,与GB2312兼容。

Unicode 编码集

ISO试图创建一个全新的语言字典,将所有的语言互相翻译。String在内存中 不需要编码格式,它只是一个Unicode字符串而已。只有当字符串需要在网络中传输或要被写入文件时,才需要编码格式。

String s = "严"; 
//编码。
byte[] b = s.getBytes("UTF-8");
//解码。 
String n = new String(b,"UTF-8"); 

6.3 对比

6.4 参考文章

(1) Java 中的字符编码方式
(2) Java 几种常见的编码格式

七、异常

Java中定义了许多异常类,并定义了Throwable作为所有异常的超类,将异常划分为两类ErrorException

八、注解

Java&Android 基础知识梳理(1) - 注解

九、容器

Java&Android 基础知识梳理(8) - 容器类

十、内存模型

Java&Android 基础知识梳理(3) - 内存区域

十一、垃圾回收

Java&Android 基础知识梳理(4) - 垃圾收集器与内存分配策略

十二、类加载

Java&Android 基础知识梳理(5) - 类加载&对象实例化

十三、泛型

十四、反射

上一篇 下一篇

猜你喜欢

热点阅读