JavaSE知识点总结

反射破坏代码的封装性,破坏原有的访问修饰符访问限制(private)
getDeclaredMethods不是会得到所有的方法,如不包括继承来的方法。

java多态有两种情况:重载和覆写
在重写中,运用的是动态单分配,是根据new的类型确定对象,从而确定调用的方法;
在重载中,运用的是静态多分派,即根据静态类型确定对象,因此不是根据new的类型确定调用的方法

重写规则
1 mSub 和mSuper都是实例方法。
2 mSub的签名是mSuper的子签名。(方法的名称和参数类型)如果有泛型,在子类泛型擦除后,需要大于父类方法的类型。如子类List list,父类List<\Number> list 这样合法,但是子类List<\Stirng> list 父类:List list 这样不合法。
3 mSub的返回类型是mSuper返回类型的可替换类型。
4 mSub的访问权限不能低于mSuper的访问权限。
5 mSub不能比mSuper抛出更多的受检异常。
6 子类Sub继承了父类的mSub方法。

List<? extends T>和List<? super T>:
1.能赋值给<? extends T>的类型,只有T的自己和它子类
2.能赋值给<? super T>的类型,只有T的自己和它的父类
3.所有的List<? extends T> 进行add操作都会报错。这是因为除null外,任何元素都不能被添加到<? extends T>内。
4.List<? super T>可以添加元素,但是只能添加T及它的子类对象。
5.所有List<? super T>集合可以执行get操作,虽然能返回对象,但是类型丢失,只能返回Object对象。
6.List<? extends T>可以返回带类型的元素,但只能返回Cat自身及其父类对象,因为子类类型被擦除了。

注意:Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定。Static方法跟类的任何实例都不相关,所以概念上不适用。

接口与抽象类的区别
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。另外,接口和抽象类在方法上有区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
3.抽象类中可以有普通成员变量,接口中没有普通成员变量
4. 抽象类中的抽象方法的访问类型可以是public,protected和默认类型
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型
7. 一个类可以实现多个接口,但只能继承一个抽象类。二者在应用方面也有一定的区别:接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码。

JDK8之后,接口可以使用default方法,会出现二义性问题:如
在这里插入图片描述
所以在类中重写方法就行了。

在这里插入图片描述

多线程产生死锁的四个必要条件:
1 互斥条件:一个资源每次只能被一个线程使用。
2 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3 不剥夺条件:进程已经获得的资源,在未使用之间,不能强行剥夺。
4 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

如何避免死锁:
破坏产生死锁的四个条件的一个即可。
指定获取锁的顺序:
1 比如某个线程只有获得A锁和B锁才能对某资源进行操作
2 获得锁的顺序是一定的,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序回去锁就可以避免死锁。
3.尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
4.尽量使用 Java. util. concurrent 并发类代替自己手写锁。
5.尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
6.尽量减少同步的代码块。

HashMap 与 HashTable的区别
1、直接父类不同
2、null值问题:HashMap的键和值都可以为null,Hashtable既不支持Null key也不支持Null value。
3、线程安全性(同步)和效率
同步意味着在一个时间点只能有一个线程可以修改hash表,任何线程在执行Hashtable的更新操作都需要获取对象锁,其他线程则等待锁的释放。
HashMap也可以实现同步,可以通过Map m = Collections.sychronizedMap(new HashMap())来达到同步的效果。
4、遍历方式不同:Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
JDK1.8之前,HashTable不是使用快速失败机制,现在两个都是快速失败。
5、初始容量不同:
Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1。
而HashMap的初始长度为16,之后每次扩充变为原来的2n。
6、计算哈希值的方法不同:
Hashtable直接使用对象的hashCode。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。
算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&) return h & (length-1);
为什么需要与数组的长度-1做与运算,如15(1111)14(1110),因为这样碰撞的概率就下降了。而且为什么要做这个运算呢?要是hashcode值是一个长度为40亿的大数字,这样是放不下的,所以对长度取模,才可以放进去。
7、底层实现
HashMap的底层是数组和链表+红黑树(链表长度大于8且table的容量大于等于64时),通过key的hashCode经过扰动函数处理后得到hash值
HashTable的底层是数组和链表。
JDK1.8 的时候ConcurrentHashMap用 Node 数组+链表+红黑树的数据结构来实现。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
HashMap的扩容机制
当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过160.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。
扩容必须满足两个条件:
1、 存放新值的时候当前已有元素的个数必须大于等于阈值
2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)

(1)、就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
(2)、当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。

LoadFactory为什么是 0.75 这个值
对于使用链表法的哈希表来说,查找一个元素的平均时间是O(1+n),这里的 n 指的是遍历链表的长度,因此加载因子越大,对空间的利用就越充分,这就意味着链表的长度越长,查找效率也就越低。如果设置的加载因子太小,那么哈希表的数据将过于稀疏,对空间造成严重浪费。

HashMap为什么能放入空值原文:https://blog.csdn.net/fenglongmiao/article/details/79656198
在put方法中:有判断keyd==null的情况,如果key为空,则进入putForNullKey()方法

// HashMap的put方法
 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
             // key为null调用putForNullKey(value)
             return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

在talbe[0]链表中查找key为null的元素,如果找到,则将value重新赋值给这个元素的value,并返回原来的value。
如果上面for循环没找到则将这个元素添加到talbe[0]链表的表头。

 /**
     * Offloaded version of put for null keys
     */
    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

HashMap
在高并发场景下,HashMap的对象丢失的原因:
1.并发赋值时被覆盖
2.已遍历区间新增元素会丢失
3.“新表被覆盖”
4.迁移丢失。在迁移过程中,有并发时,next被提前置成null

浅拷贝:
比如Person p1 = new Person();
Person p2 = new Person(p1);
复制了p1的对象,如果改变p1的基本数据类型的值,p2不会跟着改变,如果改变p1中引用类型的值,p2也会跟着改变(String除外)。
实现浅拷贝的方式可以是上面的构造方法实现,也可以通过.clone()方法,但是需要重写这个方法。
深拷贝:
与浅拷贝大概一样,但是源对象的引用类型改变之后,复制对象的引用类型不会跟着改变。这样就导致了内存消耗比浅拷贝大,每一份数据都需要复制一份。
实现深拷贝的方式可以是.clone()方法,但是需要为每一个引用类型的类都需要重写.clone()方法。所以推荐使用序列化和反序列化,实现 serializable接口。

别名:就是引用类型的传递,如果Person类有两个对象p1 p2,那实现别名就是p1=p2(这个是直接赋值,不是拷贝),如果是引用类型的赋值,改变一个的内容,另一个也会改变,如果是值的赋值,如p1.age=p2.age(值传递),改变一个,另一个不会改变

strictfp https://www.cnblogs.com/zoraliu66/p/6537066.html
关键字可应用于类、接口或方法。使用 strictfp 关键字声明一个方法时,该方法中所有的float和double表达式都严格遵守FP-strict的限制,符合IEEE-754规范。当对一个类或接口使用 strictfp 关键字时,该类中的所有代码,包括嵌套类型中的初始设定值和代码,都将严格地进行计算。严格约束意味着所有表达式的结果都必须是 IEEE 754 算法对操作数预期的结果,以单精度和双精度格式表示。
如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,可以用关键字strictfp.

native
用做java 和其他语言(如c++)进行协作时用的。
native关键字其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

public native void hello(String name);

增强for循环的缺点
1.只能对元素进行顺序访问
2.只能访问数组或集合中的所有元素
3.在循环中没有当前索引,也无法对指定元素进行操作。

复合运算符可以自动将右侧运算的结果类型转化为左侧操作数的类型。
如:
byte b = 1;
b = b+1;//错误 右边是int类型
b += 1;//正确

交换两个变量
1.通过中间变量
2.通过相加 相减 a=a+b,b = a-b,a = a-b;
3.异或,一个变量对另外一个变量异或两次,结果不变。

equals()和hashCode()
1 从Object类继承的equals()方法与“==”运算符的比较方式是相同的。如果继承的equals方法对我们自定义类不适用,需要重写。
2 重写equals需要遵循以下五条规则。
3 重写hashCode需要遵循以下三条规则。
重写equals()的规则:
1 自反性。对于任何非null的引用值x,x.equals(x)必须返回true。
2 对称性。对于任何非null的引用值x,y,当且仅当y.equals(x)返回true时,x.equals(y)也返回true。
3 传递性。对于任何非null的引用值x,y,z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也需要返回true。
4 一致性。对于任何非null的引用值x,y,假设对象上equals()比较中的信息没有被修改,则多次调用x.equals(y)方法始终返回true或false。
5 对于任何非空引用值x,x.equals(null)应返回false。
重写hashCode()的规则:
1 在应用执行期间,如果在对象equals方法比较中所用的信息没有被修改,那么在同一对象上多次调用hashCode方法时,必须一致地返回相同的整数。但如果多次执行同一个应用,不要求该该整数必须相同。
2 如果两个对象通过调用equals()方法是相等的,那这两个对象的hashCode方法必须返回相同的整数。(如果重写equals方法,但是没有重写hashCode方法则会违背第二条规则)
3 如果两个对象通过调用equals方法是不相等的,不要求它们的hashCode必须返回不同的整数。

String类的equals()方法:
先用==直接判断
比较两个对象的长度
然后就用char[]比较两个字符串的内容

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

String类:
如果String声明的时候不是常量(final),
对它操作的数据是在运行时确定的。
如:

final String s1 = "Java";
String s2 = "Java";
String s3 = s1+"SE";
String s4 = s2+"SE";

s3是常量表达式,在编译期间就会确定值。
s4不是常量表达式,在运行期才会确定值。该表达式会在运行期间通过临时创建的StringBuilder来求值(append())。

intern():
在字符串常量中,默认会将对象放入常量池;;在字符串变量中,对象是会创建在堆内存中。如果调用 intern 方法,会去查看字符串常量池中是否有等于该对象的字符串,如果没有则在常量池中新增该对象,并返回该对象引用;如果有,则返回常量池中的字符串。堆内存中原有的对象由于没有引用指向它,将会通过垃圾回收器回收。

new 和 构造器
new运算符,在程序运行时,是new运算符在堆上开辟一定得空间,然后执行对象的初始化(其中包括调用构造器),所以是先创建对象,然后进行初始化,当对象创建成功时,也是new运算符将对象的起始地址返回给应用程序。实际上构造器的作用是在对象创建的时候进行类中实例成员的初始化。
在空构造中,如果一个构造器没有使用this()来调用同类的其他构造器,则构造器的第一条语句就会使用super()来调用父类的构造器,即使我们没有显式地使用,编译器也会为我们加上。构造器递归地调用父类地构造器,直到Object为止。
因为构造器是创建对象时,用来初始化实例成员变量的,因此递归调用是必要的。因为继承的关系,子类可能会继承父类的某些成员,也就是父类的实例初始化应该在子类的实例初始化之前进行。因而先父类实例化变量,然后再子类实例化。

JDK JRE JVM 来自:https://blog.csdn.net/songkai320/article/details/51819046
JDK:在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。JDK是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。
JRE:运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。

向前引用:
注意潜在的向前引用,当向前引用变量时,取得的只能是变量尚未初始化的值。
注意如果父类的构造器调用了子类的重写的方法,注意可能引发向前引用,因为父类的初始化先于子类。
所以我们不要在构造器中调用可让子类重写的方法,应该调用本类的private或者final方法。

public class Test {
    public static void main(String[] args) {
        Square s = new Square(10);
        s.show();
    }
}
class Square{
    //private int circumference = size*4;
    private int circumference = this.size*4;
    private int size;
    private int area = initArea();
    public Square(int size){
        this.size = size;
    }
    public int initArea(){
        return size*size;
    }
    public void show(){
        System.out.println("side =" +size);
        System.out.println("circumference = " +circumference);
        System.out.println("area = "+area);

    }
}

输出:
side =10
circumference = 0
area = 0

内部类:
1.内部类需要在创建对象时绑定一个有效的外围类对象,所以内部类不能声明静态成员与静态初始块,但是final类型,值为编译时常量的静态变量除外。(使用非静态内部类就必须要先new一个这个非静态内部类的对象出来,既然都new出了一个具体的对象,就没必要再使用static修饰里面的方法和成员了。)
2.如果一个类继承了某内部类,在该类创建的时候,因为该类的父类为内部类,所以也需要一个父类外围类对象。
3.内部类会隐式声明一个外围类的引用变量,在调用内部类构造器时,就会将外围类对象赋值给这个引用变量,即令这个变量指向外围类的对象,从而实现与外围类对象的有效绑定。
4.本地类不能访问非final的局部变量或方法(构造器)参数。

Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。
而通常的内部类

加载和初始化:https://www.cnblogs.com/ityouknow/p/5603287.html
以下几种情况,类还没初始化,则会初始化该类:
1.虚拟机启动应用程序,运行包含main方法的那个类。
2.创建某个类的对象。这种创建可以是使用最常规的new来创建的,也可以是反射 克隆 序列化等方式。
3.调用类的某个静态方法
4.调用类中的某个成员变量(值为编译时常量的final静态变量除外),包括使用该变量的值或者为该变量赋值。
5.调用class类或java.lang.reflect包中的某些反射方法。
6.当子类初始化时,会首先初始化父类。
以下情况不会初始化类:
1.只是声明了类的引用,而没有实例化。
2.静态成员类初始化时,不会初始化其外围类。
3.调用classLoader的loadClass方法加载类时,不会初始化类。
4.调用类中值为编译时常量的final静态变量。
以下情况不会初始化接口:
1.调用接口中值为编译时常量的变量.
2.当类初始化时,不会初始化该类所实现的接口。
3.当接口初始化时,不会初始化该接口的父接口。
4.当接口中的嵌套类型(类,接口)初始化时,不会初始化外围接口。

类的加载包括:
1)加载:根据查找路径找到相对应的class文件,然后导入。
2)链接:链接又可以分为3个小的步骤,具体如下。
检查:检查 待加载的class文件的正确性。
准备:给类中的静态变量分配存储空间。
解析:将符号引用转换成直接引用(可选)
3)初始化:对静态变量和静态代码块执行初始化工作。

执行角度:
1.确认类元信息是否存在,当JVM接收到new指令时,首先再metaspace内检查需要创建的类元信息是否存在,若不存在,那么在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名为key进行检查对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常;如果找到,则进行类加载,并生成对应的Class类对象。
2.分配对象内存,首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小,接着在堆中划分一块内存给新对象。在分配内存空间时,需要进行同步操作,比如采用CAS失败重试,区域加锁等方式保证分配操作的原子性。
3.设定默认值。成员变量值都需要设定为默认值,即各种不同形式的零值。
4.设置对象头,设置新对象的哈希码,GC信息,锁信息,对象所属的类元信息等。这个过程的具体设置方式取决于JVM实现。
5.执行init方法。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

static成员加载的顺序是:static成员会依次从上至下的加载,而且加载时,先加载全部static成员的数据类型,并赋以默认值,然后再回过头来运行每个static成员的赋值语句!
如:https://www.cnblogs.com/chengheng0707/p/5572659.html

public class StaticDemo {
     /** 成员变量 */
     public static StaticDemo demo = new StaticDemo();
     /** 成员变量 */
     public static int i1;
     /** 成员变量 */
     public static int i2 = 0;
  
     /** 构造方法 */
     public StaticDemo() {
         i1++;
         i2++;
     }
     public static void main(String[] args) {
         System.out.println(i1);
         System.out.println(i2);
     }
}

这里输出 1 0;
代码运行流程是:
从上至下的加载,所以依次运行第3、5、7行的声明部分
加载StaticDemo demo成员变量(对象),目前不运行赋值语句,基于它是复合数据类型,则默认值为null
加载int i1成员变量,目前不运行赋值语句,基于它是基本数据类型(又名简单数据类型)的int类型,则默认值为0
加载int i2成员变量,同上,默认值为0
加载static成员的数据类型完成,回过头来,从上至下依次完成第3、5、7行的赋值部分
运行第3行的赋值语句,则调用第10行开始的构造方法,运行完成后,则i1=1, i2=1
运行第5行的赋值语句,然而这行没有赋值语句,则跳过,则i1的值依然为1,i2没有发生变化依然为1
运行第7行的赋值语句,i2的值被赋值为0
说到底,其实i2是被赋值了2次的,起初默认值为0,第1次赋值出现在构造方法中,值被改变为1,第2次出现在第7行,值被改变为0。

如果在一个类中:

static{
	sout("执行静态初始化1");
}
private static int staticX;
private static int staticY = initStaticY();
static {
	sout("执行静态初始化2");
	staticX = initStaticX();
}
public static int initStaticX(){
	sout("执行静态变量StaticX初始化");
}
public static int initStaticY(){
	sout("执行静态变量StaticY初始化");
}

以上代码输出:
执行静态初始化1
执行静态变量StaticY初始化
执行静态初始化2
执行静态变量StaticX初始化
注意的是:这里的顺序是初始化在类中声明的顺序,而不是变量在类中声明的顺序。

字节流和字符流:
区别:字节流(8bit),字符流(16bit)。字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。

序列化:
Java在序列化时不会实例化static变量。
如:

private static int i =0;
private String word = " ";
//创建该对象,并设置i = 10;word = "123",然后将对象序列化到一个文件,并在另一个JVM中读取文件,进行反序列化,读取的word = "123" i = 0; 

浮点数存在误差,禁止通过判断两个浮点型是否相等来控制某些业务流程。
在数据库中保存小数时,推荐使用decimal类型。

类关系:
继承 is-a
实现 can-do
组合 contains-a 类是成员变量
聚合 has-a 类是成员变量
依赖 use-a import类

基本数据类型和包装类型的使用规则:
1.所有的POJO类属性必须使用包装数据类型。
2.RPC方法的返回值和参数必须使用包装数据类型。
3.所有的局部变量推荐使用基本数据类型。

Java常见的异常
在这里插入图片描述

类型强制转换异常:ClassCastException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
操作数据库异常:SQLException
输入输出异常:IOException
java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.Exception
根异常。用以描述应用程序希望捕获的情况。
java.lang.IllegalAccessException
违法的访问异常。当应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常。
java.lang.IndexOutOfBoundsException
索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException
数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.RuntimeException
运行时异常。是所有Java虚拟机正常操作期间可以被抛出的异常的父类。
java.lang.StringIndexOutOfBoundsException
字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

java.lang.Error
错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况。
java.lang.ExceptionInInitializerError
初始化程序错误。当执行一个类的静态初始化程序的过程中,发生了异常时抛出。静态初始化程序是指直接包含于类中的static语句段。
java.lang.IllegalAccessError
违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.InstantiationError
实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.OutOfMemoryError
内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError
堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误。
java.lang.UnknownError
未知错误。用于指示Java虚拟机发生了未知严重错误的情况。

序列化与反序列化系列问题 原文:https://blog.csdn.net/mawming/article/details/51966428
静态成员是不能被序列化的,因为静态成员是随着类的加载而加载的,与类共存亡,并且静态成员的默认初始值都是0;
1)Java中的Serializable接口和Externalizable接口有什么区别?
这个是面试中关于Java序列化问的最多的问题。我的回答是,Externalizable接口提供了两个方法writeExternal()和readExternal()。这两个方法给我们提供了灵活处理Java序列化的方法,通过实现这个接口中的两个方法进行对象序列化可以替代Java中默认的序列化方法。正确的实现Externalizable接口可以大幅度的提高应用程序的性能。

2)Serializable接口没有方法的话,那么这么设计Serializable接口的目的是什么?
Serializable接口在java.lang包中,是Java序列化机制的核心组成部分。它里面没有包含任何方法,我们称这样的接口为标识接口。如果你的类实现了Serializable接口,这意味着你的类被打上了“可以进行序列化”的标签,并且也给了编译器指示,可以使用序列化机制对这个对象进行序列化处理。

3)什么是serialVersionUID?如果你没有定义serialVersionUID意味着什么?
SerialVersionUID应该是你的类中的一个public static final类型的常量,如果你的类中没有定义的话,那么编译器将抛出警告。如果你的类中没有制定serialVersionUID,那么Java编译器会根据类的成员变量和一定的算法生成用来表达对象的serialVersionUID ,通常是用来表示类的哈希值(hash code)。结论是,如果你的类没有实现SerialVersionUID,那么如果你的类中如果加入或者改变成员变量,那么已经序列化的对象将无法反序列化。这是以为,类的成员变量的改变意味这编译器生成的SerialVersionUID的值不同。Java序列化过程是通过正确SerialVersionUID来对已经序列化的对象进行状态恢复。

4)当对象进行序列化的时候,如果你不希望你的成员变量进行序列化,你怎么办?
这个问题也会这么问,如何使用暂态类型的成员变量?暂态和静态成员变量是否会被序列化等等。如果你不希望你的对象中的成员变量的状态得以保存,你可以根据需求选择transient或者static类型的变量,这样的变量不参与Java序列化处理的过程。

5)如果一个类中的成员变量是其它符合类型的Java类,而这个类没有实现Serializable接口,那么当对象序列化的时候会怎样?
如果你的一个对象进行序列化,而这个对象中包含另外一个引用类型的成员编程,而这个引用的类没有实现Serializable接口,那么当对象进行序列化的时候会抛出“NotSerializableException“的运行时异常。

6)如果一个类是可序列化的,而他的超类没有,那么当进行反序列化的时候,那些从超类继承的实例变量的值是什么?

Java中的序列化处理实例变量只会在所有实现了Serializable接口的继承支路上展开。所以当一个类进行反序列化处理的时候,超类没有实现Serializable接口,那么从超类继承的实例变量会通过为实现序列化接口的超类的构造函数进行初始化。

7)你能够自定义序列化处理的代码吗或者你能重载Java中默认的序列化方法吗?
答案是肯定的,可以。我们都知道可以通过ObjectOutputStream中的writeObject()方法写入序列化对象,通过ObjectInputStream中的readObject()读入反序列化的对象。这些都是Java虚拟机提供给你的两个方法。如果你在你的类中定义了这两个方法,那么JVM就会用你的方法代替原有默认的序列化机制的方法。你可以通过这样的方式类自定义序列化和反序列化的行为。需要注意的一点是,最好将这两个方法定义为private,以防止他们被继承、重写和重载。也只有JVM可以访问到你的类中所有的私有方法,你不用担心方法私有不会被调用到,Java序列化过程会正常工作。

8)假设一个新的类的超类实现了Serializable接口,那么如何让这个新的子类不被序列化?
如果一个超类已经序列化了,那么无法通过是否实现什么接口的方式再避免序列化的过程了,但是也还有一种方式可以使用。那就是需要你在你的类中重新实现writeObject()和readObject()方法,并在方法实现中通过抛出NotSerializableException。

9)在Java进行序列化和反序列化处理的时候,哪些方法被使用了?
这个是面试中常见的问题,主要用来考察你是否对readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。Java序列化是通过java.io.ObjectOutputStream这个类来完成的。这个类是一个过滤器流,这个类完成对底层字节流的包装来进行序列化处理。我们通过ObjectOutputStream.writeObject(obj)进行序列化,通过ObjectInputStream.readObject()进行反序列化。对writeObject()方法的调用会触发Java中的序列化机制。readObject()方法用来将已经持久化的字节数据反向创建Java对象,该方法返回Object类型,需要强制转换成你需要的正确类型。

10)假设你有一个类并且已经将这个类的某一个对象序列化存储了,那么如果你在这个类中加入了新的成员变量,那么在反序列化刚才那个已经存在的对象的时候会怎么样?
这个取决于这个类是否有serialVersionUID成员。通过上面的,我们已经知道如果你的类没有提供serialVersionUID,那么编译器会自动生成,而这个serialVersionUID就是对象的hash code值。那么如果加入新的成员变量,重新生成的serialVersionUID将和之前的不同,那么在进行反序列化的时候就会产生java.io.InvalidClassException的异常。这就是为什么要建议为你的代码加入serialVersionUID的原因所在了。

Arraylist和Linkedlist区别?
这两个的区别大概就是数据和链表的区别。ArrayList底层使用的数组,有容量限制,虽然它是动态扩容(扩容到原来的1.5倍),但是每次扩容都会消耗性能,对于ArrayList来说,他的查询速度快(随机加下标查询快),增删较慢。LinkedList底层是使用双向链表,不需要扩容,而且查询较慢,增删较快(增删也需要遍历到那个节点)。

怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

ArrayList实现了RandomAccess接口
此接口是一个空接口,标志着“只要实现该接口得List类,都能实现快速随机访问”

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

I/O流
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

BIO、NIO、AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

脚本语言和编译语言的区别
原文:https://www.cnblogs.com/Yzshang/p/6614103.html
抽象的级别不同: 脚本语言更抽象。在脚本语言中,存在有高级的数据结构,如列表和字典结构,和对这种结构简单方便的嵌套和操作。编译语言有比较明确的定义等等。
  类型定义不同: 脚本语言对类型的定义就比较松散,不需要类型声明,而且在运行时自动进行动态类型检查。而编译语言通常是强类型定义或静态定义,也就是说变量的类型在程序中指定了。
   执行方式不同: 脚本语言是解释成指令被立即执行。这样完全将编译过程从编辑-编译-运行循环中去掉了。而编译语言的程序被编译成可执行的二进制。
  运行速度不同: 脚本语言是解释执行的,在运行时解释每一条语句然后执行。这样比编译执行的语言要慢。而编译语言因为编译成机器码,可以直接运行,所以在运行速度上比较快。

脚本语言:
  脚本语言是一种介于标记语言和编程语言之间的语言,没有编程语言复杂、严谨的语法和规则。
  脚本语法比较简单,比较容易掌握,与编程语言也有许多相似之处,其函数与编程语言比较相似一些。与编程语言之间最大的区别是编程语言的语法和规则更为严格和复杂一些。
  脚本语言是解释性的语言,例如Python、javascript(目前接触过的)等等,它不象c\c++等可以编译成二进制代码,以可执行文件的形式存在。而脚本语言不需要编译,可以直接用,由解释器来负责解释。

编译语言
 编译语言是程序在执行之前需要一个专门的编译过程,运行时不需要重新编译,直接使用编译的结果就行了,脱离其语言环境独立执行。程序执行效率高,使用方便。但程序一旦需要修改,必须先修改源代码,再重新编译生成新的目标文件才能执行。因需依赖编译器,故跨平台性差些。

Class.forName()和ClassLoader.loadClass两个都是加载类,区别是什么?
1.Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
2.ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,
不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

Effective Java
1.静态工厂经常更加适合,因此切忌第一反应就是提供共有的构造器,而不先考虑静态工厂。
2.遇到多个构造器参数时要考虑使用构建器(Builder),也可以依次使用setter注入。
3.用私有构造器或者枚举类型强化Singleton属性

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值