java面试题总结

这篇博客汇总了Java面试中常见的知识点,包括JVM内存分区、四种引用类型、垃圾回收机制、线程池原理、反射与注解的应用,以及深拷贝与浅拷贝的区别。同时讲解了内部类的种类,如成员内部类、局部内部类、静态内部类和匿名内部类,并探讨了自动装箱与拆箱的细节。此外,还介绍了Java中的四种引用类型:强引用、软引用、弱引用和虚引用,以及CAS和AQS的原理。文章最后提到了this关键字的使用和异常处理机制。
摘要由CSDN通过智能技术生成

面试问题整理 — Java

基本数据类型的所占字节
  • 1字节 : byte 、boolean
  • 2字节 : short 、char
  • 4字节 : int 、float
  • 8字节 : long 、double
常量池
  • 常量:常量是用final修饰的成员变量,常量在类编译时期载入类的常量池中。
  • 即final修饰的成员变量(实例变量)和静态变量(静态变量也只能是用static修饰的成员变量),那么用final修饰的局部变量(方法内)我们也可以称之为不可变变量。(存储在栈中)
  1. 常量池大体可以分为:静态常量池运行时常量池
    静态常量池存在于class文件中。
    运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池值的是运行时常量池。讨论也的都是运行时常量池 。
  2. 优点
    • 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
    • 节省运行时间:比较字符串时,比equals()快。对于两个引用变量,只用 “==”判断引用是否相等,也就可以判断实际值是否相等。
  1. 常量池主要用于存放两大类常量:字面量符号引用量
    字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值(成员变量)等。
    符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符
  2. 运行时常量池
    • 在Class类文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用如不过不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。这部分内容将在类加载后进入方法区的运行时常量池中存放。
    • 运行时常量池相对于CLass文件常量池(静态常量池)的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
  3. 包装类常量池(对象池)
    • java中基本类型的包装类的大部分都实现了常量池技术,
      即Byte,Short,Integer,Long,Character,Boolean
    • intern方法,首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的。
  4. 从-128~127的数全部被自动加入到了常量池里面,意味着这个段的数使用的常量值的地址都是一样的。超过此范围都是成为新对象
自动装箱和拆箱
  1. 装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型
  2. 如果值位于[-128,127]区域中,会使用IntegerCache类缓存数据,类似于字符串常量池。
    所以如果赋的值超出这个区域, 便会创建一个新的Integer对象。(好处是平时如果频繁的使用Integer,并且数值在[-128,127]中,便不会重复创建新的Integer对象。
    但是Double和Float这两个基本数据类型的包装类就没有对应常量池(对象池)的实现。
    Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
    Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象
  3. 需要知道什么时候会引发装箱和拆箱
    Integer.valueOf():
- 先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。
  • 定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。
  • 第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。
    下面看看SMALL_VALUES[i + 128]是什么东西:

1 private static final Integer[]
SMALL_VALUES = new Integer[256];
它是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象

  1. 所以我们这里可以总结一点:装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能

int intValue:返回value值

  1. 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
  2. equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱。
  3. 当两种不同类型用= =比较时,包装器类的需要拆箱, 当同种类型用= =比较时,会自动拆箱或者装箱当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
  4. equals满足两个条件才为true:
    1、类型相同
    2、内容相同
反射与注解

注解

  1. Annotation的作用:

不是程序本身,可以对程序作出解释。
(这一点和注释(comment)没有什么区别) 可以被其他程序(比如:编译器等)读取。

  1. Annotation的格式:

注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”)。

  1. Annotation在哪里使用:

可以附加在package,class,method,filed等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。

  1. 内置注解

@Override:定义在java.lang.Override中,此注解只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。

@Deprecated:定义在java.lang.Deprecated中,此注解可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或者是存在更好的选择。

@SuppressWarning:定义在java.lang.SuppressWarning中,用来抑制编译时的警告信息。
他与前两个注解有所不同,需要添加一些参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了。
@SuppressWarning(”all“)镇压全部
@SuppressWarning(”unchecked“)镇压危险
@SuppressWarning(value={”unchecked“,”deprecation“})还可以同时镇压多种等等

  1. 元注解

元注解的作用就是负责注解其他注解,java定义了四个标准的meat-annotation类型,他们被用来提供对其他annotation类型做说明。
这些类型和它们所支持的类在java.lang.
annotation中可以找到。
@Target、@Retention、@Document、@Inherited)
@Target :用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)
@Document:说明该注解将被包含在javaDoc中文档中
@Inherited:说明子类可以继承父类中的该注解

  1. 我们也可以自定义注解,只需要在类的前面使用@interface 关键词就可以作为一个注解。
    自定义注解:

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
@interface用来声明一个注解,格式:public @interface 注解名 {定义内容}
其中的每一个方法实际上是声明了一个配置参数。 方法的名称就是参数的名称。
返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)。可以通过default来声明参数的默认值。
如果只有一个参数成员,一般参数名为value 注解元素必须要有值,我们定义注解元素时, 经常使用空字符串,0作为默认值。

  • 反射机制:
  1. Java不是动态语言,但是Java可以称之为 “准动态语言” 。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活。
  2. java反射机制概述
    Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
    加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
  3. java反射机制研究及应用
    反射机制提供的功能
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时获取泛型信息
    • 在运行时调用任意一个对象的成员方法和变量
    • 在运行时处理注解、生成动态代理
  4. 优点
    可以实现动态创建对象和编译,体现出很大的灵活性。
  5. 缺点
    性能有影响。
    使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求 。
    这类操作总是慢于直接执行的操作。
    反射相关主要API
    • java.lang.Class:代表一个类 java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器
深拷贝和浅拷贝
  • 拷贝:
  1. 引用拷贝:
    创建一个指向对象的引用变量的拷贝(拷贝后两变量指向同一个对象,地址相同)
  2. 对象拷贝:
    创建一个新的对象(拷贝后两变量所指向的地址不同
  • 深拷贝浅拷贝都是对象拷贝
    1.浅拷贝
    • 定义:
      被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享
      简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
      在这里插入图片描述
  1. 深拷贝
    • 定义:
      深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
      简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
      在这里插入图片描述
四种内部类
  1. 什么是内部类?
    在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
  2. 内部类的分类:
    成员内部类、局部内部类、静态内部类和匿名内部类。
    • 成员内部类:
      • 即作为外部类的一个成员存在,与外部类的属性、方法并列。
      • 注意:成员内部类中不能定义静态变量,但可以访问外部类的所有成员
      • 成员内部类的优点:
        • 内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使将外部类声明为PRIVATE,但是对于处于其内部的内部类还是可见的。)
        • 用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
          注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
    • 局部内部类:
      • 在方法中定义的内部类,与局部变量类似,在局部内部类前不加修饰符public或private,其范围为定义它的代码块
      • 注意:局部内部类中不可定义静态变量,可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的
      • 在类外不可直接生成局部内部类(保证局部内部类对外是不可见的)。要想使用局部内部类时需要生成对象,对象调用方法,在方法中才能调用其局部内部类。通过内部类和接口达到一个强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性
    • 静态内部类:
      • 静态内部类定义在类中,任何方法外,用static定义。
      • 注意:静态内部类中可以定义静态或者非静态的成员
      • 生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in=new Outer.Inner();
        而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类
      • 静态内部类不可用private来进行定义
    • 匿名内部类:
      • 匿名内部类就是没有名字的内部类,一种特殊的局部内部类,它是通过匿名类实现接口
      • 一个类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖
      • 只是为了获得一个对象实例,不需要知道其实际类型
      • 类名没有意义,也就是不需要使用到
      • 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类,没有类名,根据多态,我们使用其父类名。因他是局部内部类,那么局部内部类的所有限制都对其生效
      • 匿名内部类是唯一一种无构造方法类。大部分匿名内部类是用于接口回调用的。
      • 匿名内部类在编译的时候由系统自动起名Out$1.class。如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类
      • 因匿名内部类无构造方法,所以其使用范围非常的有限。当需要多个对象时使用局部内部类,因此局部内部类的应用相对比较多。
      • 匿名内部类中不能定义构造方法。如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类。
  • 内部类总结:
    1. 首先,把内部类作为外部类的一个特殊的成员来看待,因此它有类成员的封闭等级:private ,protected,默认(friendly),public,它有类成员的修饰符: static,final,abstract
    2. 非静态内部类nested inner class,内部类隐含有一个外部类的指针this,因此,它可以访问外部类的一切资源(当然包括private)
      外部类访问内部类的成员,先要取得内部类的对象,并且取决于内部类成员的封装等级
      非静态内部类不能包含任何static成员.
    3. 静态内部类:static inner class,不再包含外部类的this指针,并且在外部类装载时初始化。
      静态内部类能包含static或非static成员。
      静态内部类只能访问外部类static成员。
      外部类访问静态内部类的成员,循一般类法规。对于static成员,用类名.成员即可访问,对于非static成员,只能用对象成员进行访问。
    4. 对于方法中的内部类或块中内部类只能访问块中或方法中的final变量。
      类成员有两种static , non-static,同样内部类也有这两种non-static 内部类的实例,必须在外部类的方法中创建或通过外部类的实例来创建(OuterClassInstanceName.new innerClassName(ConstructorParameter)),并且可直接访问外部类的信息,外部类对象可通过OuterClassName.this来引用
      static 内部类的实例, 直接创建即可,没有对外部类实例的引用。
      内部类不管static还是non-static都有对外部类的引用。
      non-static 内部类不允许有static成员。
    5. 方法中的内部类只允许访问方法中的final局部变量和方法的final参数列表,所以说方法中的内部类和内部类没什么区别。但方法中的内部类不能在方法以外访问,方法中不可以有static内部类。
      匿名内部类如果继承自接口,必须实现指定接口的方法,且无参数。
      匿名内部类如果继承自类,参数必须按父类的构造函数的参数传递。
jvm分区(重点)
四种引用类型
  • Java 中有四种引用类型,分别为:
  1. 强引用(Strong Reference):如Object obj = new Object(),这类引用是 Java 程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。
  2. 软引用(Soft Reference):它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2 之后提供了SoftReference类来实现软引用。
  3. 弱引用(Weak Reference):它也是用来描述非必须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK1.2 之后,提供了WeakReference类来实现弱引用。
  4. 虚引用(Phantom Reference):也称为幻引用,最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2 之后提供了PhantomReference类来实现虚引用。
垃圾回收收集器
  • 垃圾回收(GC)线程与应用线程保持相对独立,当系统需要执行垃圾回收任务时,先停止工作线程,然后命令 GC 线程工作。以串行模式工作的收集器,称为Serial Collector,即串行收集器;与之相对的是以并行模式工作的收集器,称为Paraller Collector,即并行收集器。
  • 具体几种分类见原文链接!!!!!
  • 原文链接:https://blog.csdn.net/qq_35246620/article/details/80522720
full gc和minor gc
  1. 新生代 GC(Minor GC / Scavenge GC):
    • 发生在新生代的垃圾收集动作。
    • 因为 Java 对象大多都具有朝生夕灭的特性,因此 Minor GC 非常频繁(不一定等 Eden 区满了才触发),一般回收速度也比较快。
    • 在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集。
  2. 老年代 GC(Major GC / Full GC):
    • 发生在老年代的垃圾回收动作。
    • Major GC 经常会伴随至少一次 Minor GC。由于老年代中的对象生命周期比较长,因此 Major GC 并不频繁,一般都是等待老年代满了后才进行 Full GC,而且其速度一般会比 Minor GC 慢10倍以上。
    • 另外,如果分配了 Direct Memory,在老年代中进行 Full GC 时,会顺便清理掉 Direct Memory 中的废弃对象。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”算法或“标记-整理”算法来进行回收。
类加载过程
  • 类的加载和ClassLoader的理解
  1. 当程序主动使用某个类时,如果该类还未加载到内存中,则系统会通过如下三个步骤来完成对该类的一个初始化
  • 加载:将class文件字节码内容加载到内存中,并将这些静态的数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。class的实例对象在这个时候自动创建,所以我们不能主动创建class对象,我们只能获取来调用它的一些方法。
  • 连接:将Java类的二进制代码合并到JVM的运行时状态之中的过程。
    – 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
    – 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
    – 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化
    执行类构造器()方法的过程。
    类构造器()方法是由编译期自动收集类中所有类变量的赋值动作静态代码块中的语句合并而成的。(类构造器是构造类信息的,不是构造该类对象的构造器)将所有static声明的方法属性和代码块合并在()方法中。
    当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    虚拟机会保证一个类的()方法在多线程环境中被正确的加锁和同步
  1. 什么时候会发生类的初始化?
  • 类的主动引动(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main()方法所在的类
    • new 一个类的对象
    • 调用类的静态成员(除了flina常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域的时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化
    • 通过数组定义类引用,不会触发此类的初始化 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
  1. 类加载器的作用
  • 类加载的作用:
    • 将class文件字节码内容加载到JVM内存中,并将这些静态数据转换成方法区的运行时数据结构 ,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
    • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
    • 类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器:
      • 引导类加载器:用C++编写的,JVM自带,主要负责加载核心的类库(java.lang.*等),用来装载核心类库。该加载器无法直接获取 Bootstap Classloader
      • 扩展类加载器:负责jre/lib/ext目录下的jre包或—D java.ext.dirs 指定目录下的jre包装入工作库 Extension ClassLoader
      • 系统类加载器:负责java —classpath或 —D java.class.path所指的目录下的类与jre包装入工作,是最常用的类加载器 System ClassLoader
  1. 双亲委派机制
  • 什么是双亲委派机制?
    • 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
    • 简单来说就是当我们自己定义一个String类,类路径为java.lang.String,这时候就通过双亲委派机制向上找,当走到引导类加载器(Bootstap Classloader)时,发现该类已被加载,那么就使用系统的类,我们自定义的就失效。多重检测,为了保证程序的安全性
  • 类加载器
    • 在介绍双亲委派机制的时候,不得不提ClassLoader(类加载器)。说ClassLoader之前,我们得先了解下Java的基本知识。
    • 我们知道java程序运行在虚拟机(JVM)中的,但他是如何运行在JVM中的呢?
      • 我们在IDE编写的java文件,首先被编译成.class字节码文件。然后由我们的classloader将这些字节码文件加载到JVM中执行。
      • 我们在IDE编译器中搜索ClassLoader类,然后找到loadClass方法

当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。
如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。
注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。
直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出 ClassNotFoundException。

  • 为什么要使用这种机制?
    • 这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入
  • 原文链接:https://blog.csdn.net/qq_43377329/article/details/115228756
线程池(重点)
  1. 线程池的优势
    • 使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
      如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
      那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务
  • 总体来说,线程池有如下的优势:
    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池创建方法
  1. 线程池的使用
    线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种
public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
参数表示的含义
  1. 需要如下几个参数:
    • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为true 时,核心线程也会超时回收。
    • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
    • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
    • unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
    • workQueue(必需)任务队列。通过线程池的 execute() 方法提交的 Runnable对象将存储在该参数中。其采用阻塞队列实现。
    • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
    • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
  • 线程池工作的原理https://blog.csdn.net/u013541140/article/details/95225769
  1. 任务提交给线程池之后的处理策略,这里总结一下主要有4点:
    • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
    • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
    • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
    • 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
  2. 线程池中的线程初始化
      - 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
      - 在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
    • prestartCoreThread():初始化一个核心线程;
    • prestartAllCoreThreads():初始化所有核心线程
  3. 任务缓存队列及排队策略
      在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。
      workQueue的类型为BlockingQueue,通常可以取下面三种类型:
      1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
      2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
      3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
  4. 任务拒绝策略
      当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
    • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
  5. 线程池的关闭
    • ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
      • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
      • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
  6. 线程池容量的动态调整
    • ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
    • setCorePoolSize:设置核心池大小 setMaximumPoolSize:设置线程池最大能创建的线程数目大小
    • 当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
  7. 如何合理配置线程池的大小
      一个比较重要的话题:如何合理配置线程池大小,仅供参考。
      一般需要根据任务的类型来配置线程池大小:
      如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
      如果是IO密集型任务,参考值可以设置为2*NCPU
      当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
应用:如何实现按照提交任务的顺序
  • 通过设置一个corePoolSize为1 的线程池
ThreadLocal
  1. 它对我们常用的Handler通信机制起着重要的支撑作用。
  2. ThreadLocal,顾名思义,线程封闭的变量,也即该变量的作用范围是以当前线程为单位,别的线程不能访问该变量。
  3. ThreadLocal对外提供了get和set方法,用于提供线程独占的变量的访问途径。
  4. 简介
  • ThreadLocal隶属java.lang包,表示线程私有变量,也可叫做线程本地变量。它为单个线程单独创立了一个副本,每个线程只可访问属于自己的变量,不可访问和修改别的线程所属的变量。
    ThreadLocal属于一个泛型类,泛型参数为变量的类型,可以通过重写initialValue方法来实现对该变量初始值的设置。
    ThreadLocal提供了以下API实现对变量的控制:
public T get(); //返回该变量
public void set(T value); //设置该变量
public void remove(); //移除该变量

单例模式
单例模式概念
  • 单例模式有以下特点:
      1、单例类只能有一个实例
      2、单例类必须自己创建自己的唯一实例。
      3、单例类必须给所有其他对象提供这一实例。
      单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
      在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
      这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
      每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
      总之,选择单例模式就是为了避免不一致状态,避免政出多头。
  • 延申:
    1. 单例模式之懒汉式
      众所周知,懒汉式是非线程安全的,因为懒汉式是在第一次调用的时候才初始化,在多线程下,会导致多个线程调用被多次初始化,所以在多线程下需要给懒汉式加锁
      • 整体而言使懒汉式变为了单线程了,效率稍微差一些
      • 原因:第一个线程获取到锁后,使得后面所有的线程都在等待获取锁
    2. 双重检查效率更高
      原因:如果前面有线程已经初始化了,这样就让后面的线程直接跳过获取锁的阶段,效率更高
  • 原文链接:JAVA设计模式之单例模式
哪些情况可以破解单例模式
  • Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
  • 通过Java反射机制是能够实例化构造方法为private的类,基本上会使所有的Java单例实现失效。
    • 思路:因为在单例模式中的构造器是私有的,在Singleton类外部是不能随便调用的,但是通过反射就不一定了,通过反射模式中创建实例可以得到构造器的访问权
    • 如何应对呢?
      即便是通过反射来创建实例,也是调用类中的构造器来实现的,所以我们可以在构造器中做文章。
      先判断该单例是否被创建,单例若已被创建则抛出异常。很显然报异常了,这样便防止了这种方法实现的单例模式被反射破坏。
      饿汉式实现的单例模式都可以这样来防止单例模式被反射破坏。
      懒汉式实现的单例模式是不可以防止被反射破坏的
      用双重检查锁式实现的单例模式来进行测试,其他的懒汉式实现的单例模式同理。
      改造Singleton类中的私有构造器为先判断再创建。
  • 避免反射破坏单例模式,详见如下链接:
  • 反射破坏单例模式以及如何防御
单例模式的线程安全手写
  1. 饿汉(第三种)
    Java代码 :
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}
  • 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
  1. 静态内部类(第五种):
    Java代码 :
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  
  1. 这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种方式不同的是(很细微的差别):第三种和是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化
    因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。
    想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三方式就显得很合理。
oom异常的排查
理解继承与多态
  1. 什么是继承,继承的特点
    子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。
    特点:在继承关系中,父类更通用、子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为。
    在继承关系中。父类和子类需要满足is-a的关系。子类是父类。
    表示父类和子类的术语:父类和子类、超类和子类、基类和派生类,他们表示的是同一个意思。
  2. 为什么需要继承?什么时候应该继承
    使用继承可以有效实现代码复用,避免重复代码的出现。
    当两个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它两个类继承这个父类。
    继承实现了面向对象的原则:write once,only once(编写一次、且编写一次
  3. 如何实现继承
    在Java语言中,用extends(扩展)关键字来表示一个类继承了另一个类。
    在父类中只定义一些通用的属性和方法。
    子类自动继承父类的属性和方法,子类中可以定义特定的属性和方法。或子类重新定义父类的属性、重写父类的方法可以获得与父类不同的功能。
  4. 什么是方法重写
    如果在子类中定义的一个方法,其名称、返回类型及参数列表正好与父类中某个方法的名称、返回类型及参数列表相匹配,那么可以说,子类的方法重写了父类的方法。
    方法重写在不同类,是实现多态的必要条件。
  5. super关键字的用法和位置,super关键字调用父类的构造方法,super关键字调用父类的方法
    • 在子类的构造方法中,通过super关键字调用父类的构造方法。
    • 如果子类中重写了父类的方法,可以通过super关键字调用父类的方法。
      位置注意:调用父类的构造方法的语句(super语句)必须是构造方法中的第一条语句。
      因为创建对象的时候,需要先创建父类对象,再创建子类对象。
    • 注意:创建对象时,先创建父类对象,在创建子类对象。如果没有显示调用父类的构造方法,将自动调用父类的无参构造方法。
    • 一切类的老大(祖先)Object。
      所有类都直接或者间接地继承了java.lang.Object类,Object类中定义了所有的java对象都具有的相同行为,是所有类的祖先。
      一个类如果没有使用extends关键字,那么这个类直接继承自Object类。
  6. 什么是多态
    多态的特征是表现出多种形态,具有多种实现方式。或者多态是具有表现多种形态的能力的特征。或者同一个实现接口,使用不同的实例而执行不同的操作。
  7. 为什么需要使用多态?多态的好处
    可以增强程序的可扩展性及可维护性,使代码更加简洁。
    不但能减少编码的工作量,也能大大提高程序的可维护性及可扩展性。
  8. 如何实现多态
    • 一般做法是:写一个方法,它只接收父类作为参数,编写的代码只与父类打交道。调用这个方法时,实例化不同的子类对象(new 一个对象)。
      更具体的说:
      • 子类重写父类的方法。使子类具有不同的方法实现
      • 父类类型作为参数类型,该父类及其子类对象作为参数转入。
      • 运行时,根据实际创建的对象类型动态决定使用哪个方法
    • 在运行时,java虚拟机会根据实际创建的对象类型决定使用那个方法。一般将这称为动态绑定
    • 多态小结:多态与继承、方法重写密切相关,我们在方法中接收父类类型作为参数,在方法实现中调用父类类型的各种方法。当把子类作为参数传递给这个方法时,java虚拟机会根据实际创建的对象类型,调用子类中相应的方法(存在方法重写时)。
多线程(重点):wait()和sleep()的区别、sychornized和lock的区别、notify()和wait()底层实现
  1. sychronized
  • 线程同步
    • 优点:同步的方式,解决了线程的安全问题。
    • 缺点: 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
  • 操作共享数据的代码,即为需要被同步的代码。
  • 共享数据:多个线程共同操作的变量。
  • 同步监视器,俗称:。任何一个类的对象,都可以充当锁。
    要求:多个线程必须要共用同一把锁。在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
  1. Lock锁
    Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
    Lock只有代码块锁,synchronized有代码块锁和方法锁。
    使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
  • 优先使用顺序: Lock -> 同步代码(已经进入了方法体,分配了相应资源) -> 同步方(在方法体之外)
  1. synchronized 与 Lock的异同?
    相同:二者都可以解决线程安全问题
    不同:
    • synchronized是关键字,属于JVM层面,Lock是jdk5以后的一个具体的类,属于API层面
    • synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
      ReentrantLock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
    • synchronized不可中断,除非抛出异常或者正常运行完成
      ReentrantLock 可中断,
      • 设置超时方法trylock(long time,TimeUnit unit)
      • lockInterruptibly()放代码块中,调用interrupt()方法可中
    • synchronized非公平锁
      ReentrantLock 两者都可以,默认非公平锁
    • synchronized只能随机唤醒或者唤醒全部线程
      ReentrantLock 可以用Condition进行分组唤醒需要唤醒的线程,可以精确唤醒
  2. sleep() 和 wait()的异同?
    1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
    2.不同点:
    • 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    • 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    • sleep()可以设置时间自动唤醒,wait()需要notify()和notifyAll()唤醒
    • 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
多线程和单线程的运用场景
  1. 多线程使用的主要目的在于:
    1、吞吐量:你做WEB,容器帮你做了多线程,但是他只能帮你做请求层面的。简单的说,可能就是一个请求一个线程。或多个请求一个线程。如果是单线程,那同时只能处理一个用户的请求。
    2、伸缩性:也就是说,你可以通过增加CPU核数来提升性能。如果是单线程,那程序执行到死也就利用了单核,肯定没办法通过增加CPU核数来提升性能。
并行与并发的区别
  1. 并发的关键是你有处理多个任务的能力,不一定要同时。
  2. 并行的关键是你有同时处理多个任务的能力。
hashmap(最好看一看源码,重点)
CAS,AQS原理
  1. 什么是 CAS
    • CAS 是 compare and swap 的缩写,即我们所说的比较交换。
    • cas 是一种基于锁的操作,而且是乐观锁
      在 java 中锁分为乐观锁和悲观锁。
      • 悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。
      • 而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
    • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。
    • CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
    • java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)。
  2. CAS 的会产生什么问题?
    • ABA 问题:
      比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。
      尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
    • 循环时间长开销大:
      对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
    • 只能保证一个共享变量的原子操作:
      当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
AQS
  1. AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
  2. AQS 原理概览
    • AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
    • 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
  3. 原理图:在这里插入图片描述
    • AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
    • 状态信息通过protected类型的getState,setState,compareAndSetState进行操作
  4. AQS 对资源的共享方式
    • AQS定义两种资源共享方式
      • Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
        • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
        • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
      • Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
        ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
    • 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
  5. AQS底层使用了模板方法模式
    同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
    使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
    将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
    这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
    AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。

tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。

tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。

tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

  • 默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
  • 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证state是能回到零态的。
  • 再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
  • 一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。
  • 原文链接:https://blog.csdn.net/ThinkWon/article/details/104863992
this的用法
  1. 区分成员变量和局部变量。
  2. 代表当前对象。
  3. 构造器与构造器之间的调用。
抽象类接口区别、可以用什么修饰
  • 接口(interface)和抽象类(abstract class)是支持抽象类定义的两种机制。
  1. 接口是公开的,不能有私有的方法或变量,接口中的所有方法都没有方法体,通过关键字interface实现。
  2. 抽象类是可以有私有方法或私有变量的,通过把类或者类中的方法声明为abstract来表示一个类是抽象类,被声明为抽象的方法不能包含方法体。子类实现方法必须含有相同的或者更低的访问级别(public->protected->private)。抽象类的子类为父类中所有抽象方法的具体实现,否则也是抽象类
  3. 接口可以被看作是抽象类的变体,接口中所有的方法都是抽象的,可以通过接口来间接的实现多重继承。接口中的成员变量都是static final类型,由于抽象类可以包含部分方法的实现,所以,在一些场合下抽象类比接口更有优势。
  • 相同点
    (1)都不能被实例化
    (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
  • 不同点:
    (1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
    (2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
    (3)接口强调特定功能的实现,而抽象类强调所属关系。
    (4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
    (5)接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
  • 原文链接:java中接口和抽象类的区别
  • 个人体会:
    • 抽象类仍然是类,类拥有的属性抽象类也要有,存在意义就在于对实物进行共性提取和概括。它特殊在不能有具体描述,只能是抽象的、概念性质的描述,因此不能被实例化,也不能有方法的实现(因为不能变得具体),子类继承时如不是抽象类则为具体的类,需要具体实现所有方法,同时也可理解为是由抽象逐步细化,由大众到小众的,因此访问修饰符不能更高。
    • 接口则更注重功能的延申与拓展,是分享性质的,因此是共有的,不能有其他类不可见的部分,所以不能有私有的方法或变量,同时同种性质在个体上体现是有差异的,所以不能有方法体,要尊重每个类自己的想法。
异常
  • Java异常类层次结构图:
    在这里插入图片描述
  • 概念:在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
  1. Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
  2. Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。
    例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
    这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
  3. Exception(异常):是程序本身可以处理的异常。
    Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
    注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
    • 通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
      • 可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。
        除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
      • 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。
Exception(异常)
  • 概念:这种异常分两大类运行时异常非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
运行时异常
  • 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常)
  • 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
异常处理机制:抛出异常,捕捉异常。
  1. 抛出异常
    当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
  2. 捕获异常
    在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。
    潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。
    运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。
    当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
HashMap & Map
  • map是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等。其中这四者的区别如下(简单介绍):
  1. HashMap:我们最常用的Map,HashMap的值是没有顺序的,他是按照key的HashCode来实现的,就是根据key的HashCode 值来存储数据,根据key可以直接获取它的Value,同时它具有很快的访问速度。HashMap最多只允许一条记录的key值为Null(多条会覆盖);允许多条记录的Value为 Null。非同步的。
  2. TreeMap: 能够把它保存的记录根据key排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
  3. Hashtable: 与 HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,只有hashtable是继承自Dictionary抽象类的,hashMap和treeMap都继承自AbstractMap抽象类,LinkedHashMap继承自hashMap。
  4. LinkedHashMap: 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。key和value均允许为空,非同步的。
  • 一些常识:
    • Collection与Map集合是不是继承自Object?–不是,两个都是接口,
    • Object是类,怎么可能会继承自Object,详细看java.util下的具体接口。
  • Map排序
    1. TreeMap
      TreeMap默认是升序的,如果我们需要改变排序方式,则需要使用比较器:Comparator。
      Comparator可以对集合对象或者数组进行排序的比较器接口,实现该接口的public compare(T o1,To2)方法即可实现排序,该方法主要是根据第一个参数o1,小于、等于或者大于o2分别返回负整数、0或者正整数。
List<Map.Entry<String,String>> list = new ArrayList<Map.Entry<String,String>>(map.entrySet());
        Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
            //升序排序
            public int compare(Entry<String, String> o1,
                    Entry<String, String> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
final & finally & finalize
  1. final修饰符(关键字)
    • 被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。
    • 将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。
    • 被final声明的方法也同样只能使用,即不能方法重写。
  2. finally是在异常处理时提供finally块来执行任何清除操作
    • 不管有没有异常被抛出、捕获,finally块都会被执行
    • try块中的内容是在无异常时执行到结束。
    • catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。
    • finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
  3. finalize是方法名
    • java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
    • 它是在object类中定义的,因此所有的类都继承了它。
    • 子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。
    • finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
总结中都是我在复习时阅读过的写得很好很透彻的文章,有空的话建议跟着链接去感兴趣的文里读一读喔!跟复习进度更新,隔一段时间也建议再翻一下可能会有新收获。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值