Java基础知识面试题
Java中的基本数据类型以及占用空间大小:
Byte:1字节;
Boolean:1字节;
Char:2字节;
Short:2字节;
Int:4字节;
Float:4字节;
Double:8字节;
Long:8字节
面向对象的特征有哪些:
三大特征是封装、继承和多态。

  1. 所谓封装就是把客观事务抽象封装成类:
    (1) 简单来说,一个类就是一个封装了数据以及操作这些数据的算法;
    (2) 对象对内部数据提供了不同级别的保护,以防止程序中无关部分意外地改变或错误地使用了对象的私有部分;
  2. 继承是指可以让某个类型对象获得另一个类型对象的属性和方法:
    (1) 通过继承创建的类叫子类或派生类,被继承的类叫基类、父类或超类;
    (2) 子类是对父类功能的扩展;
    (3) 实际开发中父类往往是从子类泛化而来,父类中的属性和方法是从子类中抽象出来的;
  3. 多态就是指方法或者类型在不同条件下表现出不同的状态(通过静态分派、动态分派实现)
    主要包括方法的重写、重载和泛型方面。
    要实现重写,必须得实现继承,子类方法和父类方法名一样、参数列表相同、返回值类型相同,并且子类中重写的方法的访问权限不能低于父类中方法的访问权限。子类中不能抛出父类没有抛出过的异常。
    重载是在同一个类里面,方法名相同,但参数列表不同。
    泛型实际上是指相同类型的对象执行相同的方法,产生出不同的结果。
    静态static变量和实例变量的区别:
  4. 在语法定义上:静态变量前要加static关键字。
  5. 在程序运行时:
    (1) 实例变量是属于某个对象的属性,必须创建了对象,其中的实例变量才会被分配存储空间,才能使用这个变量;
    (2) 静态变量不属于某个实例对象,而是属于类;
    (3) 静态变量可以直接使用类名来引用。
  6. Static变量属于类,不属于类的对象,不管创建多少个对,静态变量在内存中只有一个
  7. Static关键字的特点
    (1) 随着类的加载而加载
    (2) 优于对象存在,随着字节码文件加载而加载
    (3) 可以通过类名点调用
    (4) 被类的所有对象共享
    重载和重写的区别:
  8. 重写必须继承,重载不用;
  9. 重写的方法名相同、参数列表相同,重载的方法名相同,参数列表不同;
  10. 重写的方法修饰符要大于等于父类方法,重载和修饰符无关;
  11. 重写不可以抛出父类没有抛出过的一般异常,可以抛出运行时异常。
  12. 重写返回值类型要相同。
    抽象类和接口的区别:
    相同点:1.抽象类和接口类都不能被实例化(new)
    2.如果一个类继承了接口类或者抽象类,都必须要对抽象方法进行实现。
    不同点:
  13. 接口比抽象类更抽象,抽象类可以定义具体类、构造器,但是接口不能定义构造器,其中的方法必须抽象
  14. 抽象类的成员默认修饰符可以使private,默认,protected,public;接口类必须是public
  15. 一个类只能继承一个抽象类,但是可以实现多个接口。
    接口是否可以继承接口,抽象类是否可以实现接口,抽象类是否可以继承具体类:
  16. 接口可以继承接口,并且可以继承多个接口;
  17. 抽象类可以实现接口,可以实现多个接口;
  18. 抽象类可以继承具体类,但只能继承一个类。
    GC是什么,作用是什么:
    GC是垃圾回收的意思。
    Java提供GC功能可以自动回收不被引用的对象,释放其占用的内存。
    垃圾收集器会自动进行管理。
    如果请求垃圾收集,可以调用以 下方法:System.gc()或Runtime.getRunTime().gc(),这些方***通知GC尽快进行垃圾回收。
    在对象使用以后要及时释放其引用,有利于垃圾回收,可有效防止内存泄漏。
    ArrayList和LinkedList的存储区别:
  19. ArrayList采用数组形式保存对象,这种方式将对象放在连续的位置,优点是索引快,从最后插入和删除元素快,但是头部插入和删除慢;查找和修改快,增和删比较慢
    LinkedList使用双向循环链表方式存储,从头尾插入和读取数据快,从中部插入和读取数据慢。增和删比较快,查找和修改比较慢
  20. ArrayList的扩容机制还是相对容易理解的,就是在第一个添加元素时,创建一个长度为10的数组,当数组的大小大于初始容量的时候,就会进行扩容,,以1.5倍原数组的长度创建一个新数组.
    hashMap和hashTable的区别:
  21. 线程安全方面:Hm是线程不安全的,效率比较高。HT是线程安全的,效率较低
    Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用ConcurrentHashMap。
    2、针对null值得不同
    Hm允许key为null,而ht不允许这么做。虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事。
  22. 继承结构:
    Hm实现了map接口。Ht实现了map接口和dictionary抽象类
  23. 初始容量和扩容:
    (1)Hm的初始容量是16,ht的初始容量为11,,两者的默认负载因子都是0.75
    (2)hm的扩容上是当前容量的翻倍x2
    Ht的扩容是当前容量的翻倍x2+1
  24. 两者计算hash的方式不同:
    Ht:计算hash是直接使用key的hashcode 对table数组的长度进行取模
    Hm:HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
    List、map、set接口存取元素有什么特点:
  25. List:有顺序,可重复;
  26. Set:无顺序,不可重复;
  27. Map:无顺序,key不可重复。
    队列和栈的区别:
  28. 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
  29. 栈是限定只能在一端进行插入和删除操作的线性表;
  30. 队列先进先出,栈先进后出。

Break continue return 的区别

  1. break用于完全结束一个循环,跳出循环体。不管是哪种循环,一旦在循环体中遇到break,系统将完全结束循环,开始执行循环之后的代码。 break不仅可以结束其所在的循环,还可结束其外层循环。此时需要在break后紧跟一个标签,这个标签用于标识一个外层循环。Java中的标签就是一个紧跟着英文冒号(:)的标识符。且它必须放在循环语句之前才有作用。
  2. continue的功能和break有点类似,区别是continue只是中止本次循环,接着开始下一次循环。而break则是完全中止循环。
  3. return关键字并不是专门用于跳出循环的,return的功能是结束一个方法。 一旦在循环体内执行到一个return语句,return语句将会结束该方法,循环自然也随之结束。与continue和break不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。
    String、stringBuffer、stringBuilder的区别:
  4. String是字符串常量,不可变,每次对string的操作都将重新创建一个string。Stringbuffer和stringbuilder是字符串变量,可变的,可被多次修改,并不产生新的未使用对象。
  5. Stringbuilder是线程不安全的,stringBuffer是线程安全的,stringbuilder较stringbuffer有速度优势。
    如果操作少量的数据用string;多线程操作大量数据用stringbuffer;单线程操作大量数据用stringBuilder。
    String类能不能被继承:
    不能,string类被final修饰 ,final修饰的类是不能被继承的。
    ==和equals()的区别:
  6. ==是值比较,对于引用类型变量,==是判断引用的值是否是同一个对象的地址值,如果是相同对象,其内容也是一样的;
  7. Equals方法是判断对象的内容是否相同,由于其默认内部使用==来判断,所以需要重写为按照内容比较是否相同。
    • ==是一个比较运算符号,既可以比较基本数据类型,也可以比较引用数据类型,基本数据类型比较的是值,引用数据类型比较的是地址值
    • equals方法是一个方法,只能比较引用数据类型,所有的对象都会继承Object类中的方法,如果没有重写Object类中的equals方法,equals方法和==号比较引用数据类型无区别,重写后的equals方法比较的是对象中的属性
      为什么重写equals后要重写hashcode:
      hashCode的通用规定:
      在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode方法都必须始终返回同一个值。
      如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果
      如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode方法必须产生不用的结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
      由上面三条规定可知,如果重写了equals方法而没有重写hashCode方法的话,就违反了第二条规定。相等的对象必须拥有相等的hash code。
      如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
      Hashset和hashmap:
  8. Hashmap:实现map接口,使用hash算法,里面的数据是无序的,以键值对形式存储;
  9. hashSet:实现了set接口,内部封装了hashmap,也是无序的,本质上其实就是hashmap,数据存储到hashset的key部分,value部分被屏蔽了。
    Comparable和comparator接口的区别:
  10. comparable:若一个类实现了comparable接口,就意味着该类支持排序,实现public int compareTo(T o)方法。
  11. comparator:
    (1) 比较器用于实现对象任意属性进行比较大小;
    (2) 在排序时可以指定属性比较器实现任意属性排序。
  12. 在进行排序时comparable接口用于进行自然排序,而comparator接口进行自定义排序,自定义排序将更加灵活;
  13. 设计上comparable不推荐使用,因为对程序本身有侵入性
  14. Comparable 是在集合外部定义的方法实现的排序,若一个类实现了comparable接口,就意味着该类支持排序,实现public int compareTo(T o)方法
    Comparator 是在集合内部实现的排序,
    所以,如想实现排序,就需要在集合外定义Comparable接口的方法或在集合内实现Comparator接口的方法。
  15. 默认按照类中Comparable的顺序(没有就报错ClassCastException)
  • TreeSet如果传入Comparator, 就优先按照Comparator

简述HashMap的工作原理?
https://blog.csdn.net/visant/article/details/80045154)

  1. HashMap是面向查询优化的数据结构,查询性能优异。
  2. 在其内部利用数组存储数据。
  3. 插入数据时,先根据Key的HashCode计算出数组的下标位置,再利用Key的equals()方法检查是否存在重复的Key,如果不重复直接存储到数组中,如果重复就作为链表存储到散列桶中。
  4. 插入的数据和数组容量的比值大于加载因子则进行数组扩容,并重新散列,默认的加载因子为“0.75”。
  5. 查询时,先根据Key的HashCode计算出数组的下标位置,再利用Key的equals()方法检查到Key的位置,如果找到返回Key对应的Value,否则返回Null。
  6. 由于利用Key的HashCode直接计算出数据的存储位置,由于直接确定数据的存储位置,相对于其他查找方式,查询效率非常高。
    红黑树:
    红黑规则:
  7. 每个节点不是红色就是黑色;
  8. 根总是黑色的;
  9. 如果节点是红色的,则它的子节点必须是黑色;
  10. 从根节点到叶子节点的每条路径,必须包含相同数目的黑色节点
    纠正规则,将不符合红黑规则的树纠正为红黑树:
  11. 改变节点颜色;
  12. 执行旋转操作
    为什么hashmap不使用平衡树而使用红黑树:
    红黑树和平衡二叉树都是最常用的平衡二叉搜索树,他们的查找删除和修改都是o(lgn)time。
    AVL树和红黑树有几点区别:
  13. Avl树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务。
  14. 红黑树更适合插入修改密集型任务。
  15. 通常avl树的旋转比红黑树的旋转更加难以平衡和调试。
    总结:(AVL查找更快,红黑树插入和删除更快)
    1. Avl和红黑树是高度平衡的树数据结构。他们非常相似,真正的区别在于任何添加、删除操作时完成的旋转操作次数;
    2. 两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。
    3. 在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。
    4. 两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。
    最根本的一点:在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待。
    如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!
    类加载机制:

java文件通过编译器编译成.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。

  1. 类加载器什么时候才会启动:
    类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
    2、从哪个地方去加载.class文件:
    (1)本地磁盘
    (2)网上加载.class文件(Applet)
    (3)从数据库中
    (4)压缩文件中(ZAR,jar等)
    (5)从其他文件生成的(JSP应用)
  2. 类加载的过程:
    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们的顺序如下图所示:
    类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。
    (1) 加载:
    通过一个类的全限定名来获取其定义的二进制字节流;
    将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构;
    在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口。
    (2) 验证:
    验证的主要作用就是确保被加载的类的正确性。完成四个阶段的验证:
    文件格式的验证;
    元数据验证
    字节码验证
    符号引用验证
    (3) 准备:
    准备阶段主要为类变量分配内存并设置初始值。分为2点:
    类变量(static)会分配内存,但是实例变量不会;
    这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值。
    (4) 解析:
    解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。
    符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)。
    直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。
    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
    (5) 初始化:
    在初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
    ① 声明类变量是指定初始值
    ② 使用静态代码块为类变量指定初始值
    JVM初始化步骤
    1、假如这个类还没有被加载和连接,则程序先加载并连接该类
    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
    3、假如类中有初始化语句,则系统依次执行这些初始化语句
    类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
    创建类的实例,也就是new的方式
    访问某个类或接口的静态变量,或者对该静态变量赋值
    调用类的静态方法
    反射(如 Class.forName(“com.shengsiyuan.Test”))
    初始化某个类的子类,则其父类也会被初始化
    Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类。
    Jdk与javaee的区别:
    JDK就是Java的开发工具包,其中包含了Java的编译环境、运行环境和其他一些工具。而Sun公司为Java提供了三种平台,Java EE、Java SE、Java ME。这三个平台的主要区别就在于基础类库的不同,也正是因为这些基础库的不同,导致他们的针对性不同。
    Java EE是其中类库最多的,因此它主要针对服务器、Web应用等大型程序。
    Java SE则包含最普遍的类库,所以他针对的是普通Java程序。
    Java ME则进行了进一步缩减,减少了整个环境的占用空间,针对的是性能不高的嵌入式环境。
    值传递、引用传递:
    值传递:
    指的是在方法调用时,传递的参数是按值的拷贝传递;
    按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。
    引用传递:
    指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。
    传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
    但是实质上都是值传递。
    泛型:
  3. 泛型的类型擦除:
    类型参数只存在于编译期,在运行时,java虚拟机并不知道泛型的存在。
    类型擦除带来的影响:

2.List<? extends T>和List <? super T>之间有什么区别 ?
Extends规定了泛型的上界,super规定了泛型的下界。泛型通配符使用?代表具体的类型参数。
3.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
如下:

字符串拼接用什么比较快:
使用stringBuilder.append比较快,其次是StringBuffer,最后是String使用+。
String和new String()的问题:
对于String s1 = “aaaa”;
首先在栈区创建一个s的引用,然后在字符串常量池中区查找是否存在字符串的内容是“aaaa”的对象,如果存在,则直接将s指向字符串常量池中的这个对象,如果不存在,则在字符串常量池中创建一个,再将s指向这个对象。
对于String s2 = new String(“aaaa”);
不管怎样,都会在堆中创建新的对象,再检查字符串常量池中是否存在这个对象,如果不存在,再在字符串常量池中创建一个对象,此时创建了两个对象.此时s2指向堆中创建的对象。
 此时:s2!=s1
什么是反射、有什么优缺点:
 反射就是动态加载对象,并对对象进行剖析。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法。这种动态获取信息以及动态调用对象方法的功能称为java反射机制。
优点:反射可以动态创建对象,最大限度发挥了java的灵活性。
缺点:对性能有影响。反射基本上是一种解释操作,告诉JVM我们要做什么并且满足我们的需求,这类操作总是慢于直接执行java代码。

  1. 反射的基本操作:
    1). 反射:
    一个类中包含的信息有:构造器、字段、方法
    class:描述类
    Method:描述方法
    Constructor:描述构造器
    Field:描述字段
    2). 获取类的Class实例:
    类名.class;
    类的对象.getClass();
    Class.forName(“类的全限定名”)
    3). 获取9大内置类的字节码实例:
    对于8大基本数据类型和void关键字都是有字节码实例的
    通过数据类型/void.class即可。
    4).获取数组类型的字节码文件:
    如String[].class,所有具有相同元素类型和维数的数组共享同一份字节码(Class对象)
    5). 获取构造函数:
    根据不同的构造函数,Class提供了几种获取不同构造函数的方法:
    getConstructor(),getDeclaredConstructors()
    使用Constructor的newInstance(Object…initargs)方法创建对象实例。
    可以使用Class的newInstance()方法直接创建实例对象(无参)。
    6). 操作方法-Method
    通过Class对象的getMethods(),getDeclaredMethods()获取方法。
    通过Method对象的invoke(Object obj,……)执行方法。

  2. 操作字段:
    通过Class对象的getFeild(),getDeclaredFields()方法获取Field对象。
    通过setter和getter方法为属性设置值。

  3. 反射底层原理:
    主要考虑属性操作、构造器操作、方法调用。
    1)处理属性操作的底层实现:
    Field的set和get方法底层都是委托给jdk.internal.FieldAccessor实现。
    FieldAccessor接口有很多实现,通过ReflectionFactory这个工厂构造的。
    最终委托给newFieldAccessor()方法,经过一些修饰符判读后,得到最终的FieldAccessor实现。
    2)处理构造器调用的底层实现:
    Constructor的newInstance()方法调用依赖于ConstructorAccessor。
    获取ConstructorAccessor实例也是通过反射工厂类ReflectionFactory,具体依赖于newConstructorAccessor()方法。
    3)处理方法调用的底层实现:
    Method的invoke()调用依赖于MethodAccessor,获取MethodAccessor实例与上面类似,通过ReflactionFactory的newMethodAccessor()方法,最终委托给一些本地方法执行。
    谈谈classloader
    Classloader在java中有着非常重要的作用,他主要工作在class装载的加载阶段,其主要作用是从系统外部获得class二进制数据流。他是java的核心组件,所有的class都是由classloader进行加载的,classloader负责通过将class文件里的二进制数据流装载进系统,然后交给java虚拟机进行连接、初始化等操作。
    类加载器的分类:
    (1)根类加载器(BootStrapClassLoader), c++编写,加载核心库java.*
    (2)扩展类加载器(ExtensionClassLoader), java编写,主要负载加载 扩展库javax*。
    (3)应用程序类加载器(ApplicationClassLoader),java编写,加载程序所在目录
    (4)自定义类加载器(UserClassLoader),java编写,定制化加载。负责加载程序员指定的特殊目录下的字节码文件的。
    谈谈类加载器的双亲委派机制

1.自顶向上检查类是否已经加载
2.自顶向下尝试加载类
加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
https://blog.csdn.net/pang5356/article/details/107913439

  1. 作用:双亲委派是为了安全而设计的,假如我们自定义了一个java.lang.Integer类如下,当使用它时,因为双亲委派,会先使用BootStrapClassLoader来进行加载,这样加载的便是jdk的Integer类,而不是自定义的这个,避免因为加载自定义核心类而造成JVM运行错误
  2. 如何打破双亲委派模型:
    打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法
    类的加载方式以及loadClass和forName的区别:
  3. 隐式加载:new
  4. 显示加载:loadClass、forName等
    loadClass和forName的区别:
    class.forName()除了将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块。而classLoader只干一件事情,就是将.class文件加载到JVM中,不会执行static中的内容,只有在newInstance才会去执行static块。
    Java8新特性:
  5. Lambda 表达式;
  6. 重复注解;扩展了注解的应用场景;
  7. jvm新特性,使用元空间取代持久代;
  8. Stream API 简化了集合的操作,并扩展了集合的各种功能;
  9. 新的Date/Time API ;
  10. JUC 工具包扩充
  11. 新的命令行工具
    abstract不能与哪些关键字共用
  12. private
    私有不允许子类访问,但abstract支持子类访问 矛盾
  13. final
    final不让子类重写,而abstract强制重写 矛盾
  14. static
    static类.直接调用,abstract没有方法体调用无意义
    深拷贝和浅拷贝区别:
    https://www.cnblogs.com/heyjia/p/11338852.html
    深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
    浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址;
    深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
    浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
    深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
  15. 如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
  16. 如果拷贝的对象里的元素包含引用,那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。
    浅拷贝的实现方式:
  17. 通过拷贝构造方法实现浅拷贝;
  18. 通过重写clone()方法进行浅拷贝:
    (1)Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用;
    (2)使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException.
    对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

实现深拷贝的方式:

  1. 通过重写clone方法来实现深拷贝:
    为对象的每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。每一层的每个对象都进行浅拷贝=深拷贝。
  2. 通过对象序列化实现深拷贝:
    将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

java io模型(重要):

  1. 同步和异步:
    (1)同步就是:如果有多个任务要发生,这些任务或者事件必须逐个地进行,一个事件或者任务的执行会导致整个流程的暂时等待,这些事件没有办法并发地执行;
    (2)异步就是:如果有多个任务发生,这些事件可以并发地执行,一个事件或者任务的执行不会导致整个流程的暂时等待。
  2. 阻塞和非阻塞:
    (1)阻塞就是:当某个任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;
    (2)非阻塞就是:当某个任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待。
  3. 阻塞io与非阻塞io:
    当用户线程发起一个IO请求操作,内核会去查看要读取的数据是否就绪,对于阻塞IO来说,如果数据没有就绪,则会一直在那等待,直到数据就绪;对于非阻塞IO来说,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才完成了一个完整的IO读请求操作,也就是说一个完整的IO读请求操作包括两个阶段:

1)查看数据是否就绪;

2)进行数据拷贝(内核将数据拷贝到用户线程)。

那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息。
Java中传统的IO都是阻塞IO
4. 同步io和异步io:
《Unix网络编程》:同步IO即 如果一个线程请求进行IO操作,在IO操作完成之前,该线程会被阻塞;而异步IO为 如果一个线程请求进行IO操作,IO操作不会导致请求线程被阻塞。
事实上,同步io和异步io模型是针对用户线程和内核的交互来说的:
(1)对于同步IO:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程;
(2)而异步IO:只有IO请求操作的发出是由用户线程来进行的,IO操作的两个阶段都是由内核自动完成,然后发送通知告知用户线程IO操作已经完成。也就是说在异步IO中,不会对用户线程产生任何阻塞。
5. 五种io模型:
(1)阻塞io模型:
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除阻塞状态。
(2)非阻塞io模型:
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
(3)多路复用io:
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
(4)信号驱动io模型:
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
(5)异步io模型:
异步io模型其实是最理想的io模型:
IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。
java静态分派、动态分派:
静态分派和动态分派是java多态机制的原理。

  1. 静态分派:

这段代码输出结果为this is father。在编译阶段,由于father变量类型被声明为Father。因此在编译阶段就已经确定了调用的是参数Father的方法,与具体的实例化对象无关。
编译器在编译期并不知道一个对象的实际类型是什么。编译器在重载时是通过参数的静态类型而不是实际类型作为判断依据。
并且静态类型在编译期可知,因此,编译器会根据参数的静态类型决定使用哪个重载版本。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型用法是方法重载。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。
2. 动态分派:

这里输出结果是son,这里牵扯到动态分派的问题,对于方法重写,java采用动态分派机制,也就是在运行时才确定调用哪个方法。由于father的实际类型是Son,因此调用的就是Son的name方法。
我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
java语言是一种静态多分派、动态单分派的语言。
3. 单分派和多分派:
java在动态运行时就是根据调用者最终类型来区分要方法调用的方法的,所以说java是单分派语言。然而java在编译时是根据调用者本身类型和方法参数共同确定调用方法的,所以是静态多分派。

谈谈你对java的理解
1.平台无关性 一处编译,到处运行

Java首先编译成字节码文件,再由不同平台的jvm进行解析,java语言在不同的平台上运行时,不需要重新编译,java虚拟机在执行字节码文件的时候,把字节码转换成具体平台上的机器指令。
Jvm如何加载.class文件

2.GC
3.语言特性:泛型 反射 lambda表达式
4.面向对象:封装 继承 多态
5.类库:集合 并发 io
6.异常处理
This和super的区别和应用

  • A:this和super都代表什么
    • this:代表当前对象的引用,谁来调用我,我就代表谁
    • super:代表当前对象父类的引用
  • B:this和super的使用区别
    • a:调用成员变量
      • this.成员变量 调用本类的成员变量,也可以调用父类的成员变量
      • super.成员变量 调用父类的成员变量
    • b:调用构造方法
      • this(...) 调用本类的构造方法
      • super(...) 调用父类的构造方法
    • c:调用成员方法
      • this.成员方法 调用本类的成员方法,也可以调用父类的方法
      • super.成员方法 调用父类的成员方法