Java基础面试题
### Java 基础##### 1. 面向对象和面向过程的区别
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
**面向过程**
**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,像是单片机、嵌入式开发一般采用面向过程开发,性能是最重要的因素。
**缺点:** 没有面向对象易维护、易复用、易扩展
**面向对象**
**优点:** 易维护、易复用、易扩展,因为面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
**缺点:** 性能比面向过程低
##### 2. Java 语言有哪些特点
简单易学、面向对象、跨平台、编译与解释并存、支持多线程、支持网络编程并且很方便
##### 3. 什么是 JDK 什么是 JRE 什么是 JVM 三者之间的联系与区别
JDK 里面包含了 JRE,JRE里面包含了JVM,JDK负责编译和调试程序,JRE负责运行程序,JVM 负责将字节码转换为特定机器代码,它是java语言的核心并且具有平台独立性。
##### 4. 什么是字节码 采用字节码的最大好处是什么
字节码文件,也就是计算机可以识别的二进制文件
最大好处是:可以实现一次编译到处运行,也就是跨平台
##### 5. Java 和 C++ **的区别**
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 有自动内存管理机制,不需要程序员手动释放无用内存
- Java 的类是单继承的,C++ 支持多重继承
##### 6. Java 的四个基本特性
抽象、封装、继承、多态
##### 7. Java 的8种基本数据类型有哪些?
**四种整形数据:byte、short、int、long**
byte:8 位,用于表示最小数据单位,如文件中数据,-128~127
short:16 位,很少用,-32768 ~ 32767
int:32 位、最常用,-2^31-1~2^31 (21 亿)
long:64 位、次常用 注意事项: int i=5; // 5 叫直接量(或字面量),即 直接写出的常数。 整数字面量默认都为 int 类型,所以在定义的 long 型数据后面加 L或 l。 小于 32 位数的变量,都按 int 结果计算。 强转符比数学运算符优先级高。见常量与变量中的例子。
**两种浮点数类型:float、double**
float:32 位,后缀 F 或 f,1 位符号位,8 位指数,23 位有效尾数。
double:64 位,最常用,后缀 D 或 d,1 位符号位,11 位指数,52 位有效尾
**一种字符类型:char**
16 位,是整数类型,用单引号括起来的 1 个字符(可以是一个中文字符)
**一种布尔类型:boolean**
##### 8. 字符型常量和字符串常量的区别
2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算,字符串常量代表一个内存地址
3. 内存:字符常量只占2个字节,字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**)
##### 9. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗 为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
##### 10. 构造器 Constructor 是否可被 override
父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
##### 11. 构造方法有哪些特性
1. 名字与类名相同;
2. 没有返回值,不能用 void 声明构造函数;
3. 生成类的对象时自动执行,无需调用。
##### 12. 重载和重写的区别
**重载:** 发生在同一个类中,方法名必须相同,参数类型、参数数量、参数顺序,方法返回值和访问修饰符可以不同,发生在编译时。
**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围 和抛出的异常范围 小于等于父类,访问修饰符范围大于等于父类。
##### 13. Java 面向对象编程三大特性:封装、继承、多态
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。
封装可以隐藏对数据操作的具体细节,安全性有保障,可以更方便的调用封装的部分。
继承
继承是使用已存在的类作为基础建立新类的技术,新的类的可以增加新的数据或新的功能,也可以用父类的功能,通过继承我们能够非常方便地复用以前的代码。
**关于继承如下 3 点请记住:**
1. 子类拥有父类非 private 的属性和方法。
2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3. 子类可以用自己的方式实现父类的方法。
多态
就是在程序中定义的变量,和这个变量用的方法,并不确定是属于哪个类的实例,只有在程序运行时才确定的。
在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
##### 14. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的
**可变性**
String 类中使用 final 关键字 字符数组 保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用 字符数组 保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的。
**线程安全性**
String 中的对象是不可变的,线程安全。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
**性能**
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
**对于三者使用的总结:**
1. 操作少量的数据 = String
2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
##### 15.自动装箱与拆箱
**装箱**:将基本类型用它们对应的引用类型包装起来;
**拆箱**:将包装类型转换为基本数据类型;
```java
Integer i = 10; // 装箱,这个过程中会自动根据数值创建对应的 Integer对象
int n = i; // 拆箱,就是自动将包装器类型转换为基本数据类型
```
##### 16. 在一个静态方法内调用一个非静态成员为什么是非法的
因为非静态的成员变量和方法必须通过对象调用,静态方法在类被加载的时候已经执行了,这个时候对象还没有呢,所以它是非法的。
##### 17. 在 Java 中定义一个不做事且没有参数的构造方法的作用
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,就会调用父类中【没有参数的构造方法】。所以,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
##### 18. import java和javax有什么区别
没有区别
刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。但是后来 javax 慢慢地成为了 Java API 的组成部分。但是,将 javax 包移动到 java太麻烦了,会破坏一堆现有的代码。所以最终决定将 javax 包将成为标准API的一部分。
所以,实际上java和javax没有区别。这都是一个名字。
##### 19.接口和抽象类的区别是什么
0. 从设计思想来说,接口是行为的抽象,是一种行为的规范,抽象类是对类的抽象,是一种模板设计。
1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
3. 一个类可以实现多个接口,但最多只能实现一个抽象类
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5. 接口不能用 new 实例化,但可以声明,而且必须引用已经实现这个接口的对象
**为什么用接口而不用抽象类**
1. 通过接口可以实现不相关类的相同行为,而不需要了解对象所对应的类。
2. 通过接口可以指明多个类需要实现的方法。
3. 通过接口可以了解对象的交互界面,而不需了解对象所对应的类。
##### 20.成员变量与局部变量的区别有那些
1. 成员变量属于类,局部变量属于方法,成员变量可以被 public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符 和 static 修饰;但是,成员变量和局部变量都能被 final 所修饰;
2. 成员变量保存在堆内存,局部变量保存在栈内存
3. 成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。
##### 21. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
使用 new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球); 一个对象可以有多个引用指向它(可以用n条绳子系住一个气球)。
##### 22. 什么是方法的返回值?返回值在类的方法里的作用是什么?
方法的返回值就是方法执行后产生的结果!返回值的作用:可以根据返回结果可以进行下一步的操作。
##### 23. 静态方法和实例方法有何不同
1. 调用静态方法时,可以通过类调用,也可以通过对象调用。也就是说,调用静态方法可以无需创建对象。
2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法就没有这种限制.
3. 静态方法在JVM加载类的时候就已经执行了,实例方法在被对象调用的时候才会执行
##### 24. 对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。
##### 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
帮助子类做初始化工作。
##### 26.== 与 equals (重要)
**==** : 它的作用是判断两个对象的地址是不是相等。
基本类型:比较的就是值是否相同
引用类型:比较的就是内存地址是否相同
**equals()** :
引用类型:默认情况下,比较的是地址值。
注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同
**说明:**
- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
##### 27. hashCode 与 equals(重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
**hashCode()介绍**
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。作用就是确定该对象在哈希表中的 索引位置。hashCode() 定义在 Object 类中,所以 Java 中的任何类都包含有hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
**为什么要有 hashCode**
**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经存在的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals 方法来检查对象的值是否相同。如果两者相同,HashSet 就不会让它加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals 的次数,相应就大大提高了执行速度。
**hashCode()与equals()的相关规定**
1. 如果两个对象相等,则hashcode一定也是相同的
2. 两个对象相等,对两个对象分别调用equals方法都返回true
3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
##### 28. 简述线程,程序、进程的基本概念。以及他们之间关系是什么
**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源像是 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
##### 29. 线程有哪些基本状态?这些状态是如何定义的?
1. **新建(new)**:新创建了一个线程对象。
2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。
3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了 cpu 时间片(cpu timeslice),暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM 会把该线程放 入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
(三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
就像排队上厕所,前面的人进去了,把门关上了,这就是阻塞,外面的人只能等待。
##### 30 关于 final 关键字的一些总结
final关键字主要用在三个地方:变量、方法、类。
1. 对于一个final变量,如果是基本数据类型的变量,它的数值一旦在初始化之后就不能更改;如果是引用类型的变量,它在初始化之后就无法指向另一个对象。
2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
3. 使用final方法的原因有两个。
第一个原因是把方法锁定,防止任何继承类修改它的含义;
第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。
##### 31 Java 中的异常处理
Java里面,所有的异常都继承于 **java.lang .Throwable类**。
Throwable 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,它们都是 Java 异常处理的重要子类,各自都包含大量子类。
**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码执行的操作无关,它表示的是码运行时 JVM(Java 虚拟机)出现的问题。当虚拟机发生 Error 错误的时候,JVM 会终止线程。
**Virtual MachineError** Java虚拟机运行错误
**OutOfMemoryError**内存溢出
**NoClassDefFoundError** 找不到类
**Exception(异常):是程序本身可以处理的异常**。
**RuntimeException** 运行时异常
**NullPointerException**空指针异常
**ArithmeticException** 算术运算异常,一个整数除以0时,抛出该异常
**ArrayIndexOutOfBoundsException** 下标越界异常
**异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。**
**Throwable类常用方法**
- **public string getMessage()**:返回异常发生时的详细信息
- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息
- **public string toString()**:返回异常发生时的简要描述
- **public string getLocalizedMessage()** 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage() 返回的结果相同
**异常处理总结**
- try 块:用于捕获异常。其后可接零个或多个catch块,但是必须跟一个finally块。
- catch 块:用于处理try捕获到的异常。
- finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
**在以下4种特殊情况下,finally块不会被执行:**
1. 在finally语句块中发生了异常。
2. 在前面的代码中用了 System.exit() 退出程序。
3. 程序所在的线程死亡。
4. 关闭CPU。
##### 32 Java序列话中如果有些字段不想进行序列化 怎么办
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
##### 33 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个 public 的类,并且 public 的类名必须与文件名相一致。
##### 34 &和&&的区别
**相同点:**&和&&都可以用作逻辑与的运算符
**不同点:**
1. &&具有短路的功能,而&不具备短路功能。
2. 当&运算符两边的表达式的结果都为true时,整个运算结果才为true。
而&&运算符第一个表达式为false时,则结果为false,不再计算第二个表达式。
3. &还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,他就会执行位与运算
(我们通常使用0x0f 来与一个整数进行&运算,来获取该整数的最低4个bit位,例如:0x31 & 0x0f的结果为0x01)
##### 35 Collection 和 Collections的区别
Collection是集合类的上级接口,继承与他的接口主要有Set 和List。
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作
##### 36 Java中,是否可以继承String类?为什么?
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
##### 37 char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量可以存储汉字。
不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
##### 38 Integer与int的区别
int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。
int的默认值为0,而Integer的默认值为null,所以Integer可以区分出未赋值和值为0的区别
##### 39 什么反射?可以用来干什么?
反射就是可以动态的获取类的信息,动态调用对象的方法的功能,这种功能就叫反射。
可以用来干什么?
对于任意一个类,我都能够知道它有那些属性和方法; 对于任何一个对象,都能调用它的任何一个方法和属性;
反射机制优缺点
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。
##### 40 什么是对象序列化,什么是反序列化
1. 对象序列化,将对象中的数据编码转为字节编码的过程
2. 反序列化,将字节编码转为 Java 对象的过程
##### 41 List, Set, Map 是否继承自 Collection 接口
答: List,Set是,Map不是
##### 42 SWITCH能使用String吗?
JDK 1.7开始就支持使用String类了
##### 43 深拷贝、浅拷贝
1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递的拷贝,此为浅拷贝。
2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
##### 44. HashCode 为什么会有重复的
因为HashCode只是特征编码,就是用少量的字符组合描述比较复杂的东西,这样就会有重复几率。
##### 45. 什么是面向对象?什么是面向过程?
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象的方法主要是把事物给对象化,包括其属性和行为。面向对象编程更贴近实际生活的思想。总体来说面向对象的底层还是面向过程,面向过程抽象成类,然后封装,方便使用就是面向对象,(万物皆对象)。
1.什么是面向对象技术?
面向对象技术是一种以对象为基础,以事件或消息来驱动对象执行处理的程序设计技术。它具有抽象性、封装性、继承性及多态性。
2.面向对象与面向过程程序设计有什么不同?
(1)面向过程程序设计方法采用函数(或过程)来描述对数据的操作,但又将函数与其操作的数据分离开来;面向对象程序设计方法将数据和对数据的操作封装在一起,作为一个整体来处理。 函数与数据是否分离
(2)面向过程程序设计方法以功能为中心来设计功能模块,难于维护;而面向对象程序设计方法以数据为中心来描述系统,数据相对于功能而言具有较强的稳定性,因此更易于维护。
(3)面向过程程序的控制流程由程序中预定顺序来决定;面向对象程序的控制流程由运行时各种事件的实际发生来触发,而不再由预定顺序来决定,更符合实际需要。 预定顺序;由运行时各种事件的实际发生来触发
(4)面向对象程序设计方法可以利用框架产品(如MFC,Microsoft Foundation Classes)进行编程。 面向对象可利用框架 面向对象和面向过程的根本差别,在于封装之后,面向对象提供了面向过程不具备的各种特性,最主要的,就是继承和多态。
##### 46. 泛型
泛型:**把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型**
参数化类型:
- **把类型当作是参数一样传递**
- **`<数据类型> `** **只能是引用类型**
为什么需要泛型?
**早期 Java 是使用 Object 来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全**
首先,我们来试想一下:没有泛型,集合会怎么样
- Collection、Map集合对元素的类型是没有任何限制的。**本来我的Collection集合装载的是全部的Dog对象,但是外边把 Cat 对象存储到集合中,是没有任何语法错误的。**
- 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在 get() 的时候,返回的是 Object。**外边获取该对象,还需要强制转换**
有了泛型以后:
- 代码更加简洁【不用强制转换】
- 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
- 可读性和稳定性【在编写集合的时候,就限定了类型】
1. 类型安全。
泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2. 消除强制类型转换。
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3. 潜在的性能收益。
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
4. 更好的代码复用性,比如实现泛型算法
在框架设计时候,BaseDao<T>、BaseService<T>、BaseDaoImpl<T>、BaseServiceImpl<T>;通过继承,实现抽象了所有公共方法,避免了每次都要写相同的代码。
3、泛型使用场景
1)集合
2)泛型类
3)泛型接口
4)泛型方法
##### 47. 用过哪些集合?在项目的什么场景下使用的?
ArrayList、HashMap 使用得比较多
在金诺达化妆品追溯下有这么一个场景:
根据工厂配方id获取配方数据保存到 ArrayList,然后将配方数据根据工厂配方id分组,将数据保存到 HashMap
因为有大量的随机读,所以使用了 ArrayList
##### int 的最大值是什么?
2 的31次方
研究这个有意义吗?项目中经常会遇到这个,特别是游戏项目。
##### 48. int 字节在32位 64位系统的区别
int 在32位环境中,取值范围是 `-(2^31) ~ (2^31-1)` (-2147483648~2147483647)。
第一,为什么是31次幂而不是32次幂?因为有一位是符号位,代表正负。
第二,为何最小值为 `-(2^31)` 而最大值为 `(2^31-1)`?因为最大值为
01111111111111111111111111111111
等于 2^31-1,而00000000表示0,10000000就可以不用表示0,用来表示最小值。故最小值用补码可以表示成
10000000000000000000000000000000;
##### 49 静态方法是否是线程安全的?
如果静态方法里面没有使用静态字段,它就是线程安全的。
### Java 8 新特性
lomdba 表达式
stream 流
时间日期
### Java Web
##### 1. Cookie和Session的的区别
cookie 保存在客户端,session保存在服务器端,
cookie 目的可以跟踪会话,也可以保存用户喜好或者保存用户名密码
cookie 被禁用了,session可以存放在哪里?
1. 通过 URL 重写
2. WebStorage 的 localStorage 或 sessionStorage
3. 通过 JWT,前端生成 token,无需再保存?
##### 2. JSP和Servlet是什么关系,有什么区别
Servlet 是一个特殊的 Java 程序,它运行在JVM里面,能够依靠服务器的支持向浏览器提供显示内容。
JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生成。
Servlet 和 JSP 最主要的不同点在于:
JSP侧重于视图,Servlet更侧重于控制逻辑。
Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML分离开来。
而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
##### 3. JSP的9个内置对象
1. request:封装客户端的请求,其中包含来自GET或POST请求的参数;
2. response:封装服务器对客户端的响应;
3. page:JSP页面本身(相当于Java程序中的this);
4. pageContext:通过该对象可以获取其他对象;
5. session:封装用户会话的对象;
6. exception:封装页面抛出异常的对象。
7. application:封装服务器运行环境的对象;
8. out:输出服务器响应的输出流对象;
9. config:Web应用的配置对象;
##### 4. JSP中的四种作用域
JSP中的四种作用域包括page、request、session和application,具体来说:
- **page** 的作用范围仅限于用户请求的当前页面
- **request** 的范围在跳转前后的两页。但是只要刷新页面,它们就重新计算了。
- **session** 的有效范围是当前会话。就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。
- **application** 它的有效范围是整个应用。整个应用是指从应用启动到结束。
##### 5. 转发(Forward)和重定向(Redirect)的区别
**转发是服务器行为,重定向是客户端行为。**
转发浏览器的地址栏不变,重定向浏览器的地址栏改变;
转发浏览器只做了一次请求。重定向浏览器至少做了两次请求;
##### 6. get和post请求的区别
1. get请求用来从服务器上获得资源,而post是用来向服务器提交数据;
2. get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
3. get传输的数据要受到URL长度限制(最大长度是 2048 个字符);而post可以传输大量的数据,上传文件通常要使用post方式;
4. 使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;
5. get使用MIME类型 `application/x-www-form-urlencoded` 的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。
补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。
##### 7. Servlet总结
在Java Web程序中,**Servlet**主要负责接收用户请求**HttpServletRequest**,在**doGet()**,**doPost()**中做相应的处理,并将回应**HttpServletResponse**反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用**init()方法**,销毁时调用**destroy()方法**。**Servlet需要在web.xml中配置**(MyEclipse中创建Servlet会自动配置),**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。
##### JSP工作原理
JSP是一种Servlet,但是与HttpServlet的工作方式不太一样。HttpServlet是先编译为class文件后部署到服务器下,是先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类(接口Servlet的一个子类)。这个类会被服务器临时存放在工作目录里面。
由于JSP只会在客户端第一次请求的时候被编译 ,因此第一次请求JSP时会感觉比较慢,之后就会感觉快很多。如果把服务器保存的class文件删除,服务器也会重新编译JSP。
开发Web程序时经常需要修改 JSP。Tomcat能够自动检测到 JSP 程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP,而不需要重启Tomcat。这种自动检测功能是默认开启的,检测改动会消耗少量的时间,在部署Web应用的时候可以在web.xml中将它关掉。
##### 阐述Servlet和CGI的区别?
##### CGI的不足之处:
1,需要为每个请求启动一个操作CGI程序的系统进程。如果请求频繁,这将会带来很大的开销。
2,需要为每个请求加载和运行一个CGI程序,这将带来很大的开销
3,需要重复编写处理网络协议的代码以及编码,这些工作都是非常耗时的。
##### Servlet的优点:
1,只需要启动一个操作系统进程以及加载一个JVM,大大降低了系统的开销
2,如果多个请求需要做同样处理的时候,这时候只需要加载一个类,这也大大降低了开销
3,所有动态加载的类可以实现对网络协议以及请求解码的共享,大大降低了工作量。
4,Servlet能直接和Web服务器交互,而普通的CGI程序不能。Servlet还能在各个程序之间共享数据,使数据库连
接池之类的功能很容易实现。
补充:Sun Microsystems公司在1996年发布Servlet技术就是为了和CGI进行竞争,Servlet是一个特殊的Java程序,一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行,它是在Servlet容器中运行的,容器将用户的请求传递给Servlet程序,并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病,然而Fast CGI早就已经解决了CGI效率上的问题,所以面试的时候大可不必信口开河的诟病CGI,事实上有很多你熟悉的网站都使用了CGI技术。
##### Servlet接口中有哪些方法及Servlet生命周期探秘
Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期相关**:
- **void init(ServletConfig config) throws ServletException**
- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException**
- **void destory()**
- java.lang.String getServletInfo()
- ServletConfig getServletConfig()
**生命周期:**
1.加载和实例化
2.初始化
3.请求处理
4.服务终止
Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。
##### 什么情况下调用doGet()和doPost()
Form标签里的method的属性为get时调用doGet(),为post时调用doPost()。
##### 转发(Forward)和重定向(Redirect)的区别
**转发是服务器行为,重定向是客户端行为。**
转发浏览器的地址栏不变,重定向浏览器的地址栏改变;
转发浏览器只做了一次请求。重定向浏览器至少做了两次请求;
**转发(Forword)**
通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。
```java
request.getRequestDispatcher("login_success.jsp").forward(request, response);
```
**重定向(Redirect)**是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 `HttpServletResponse` 的 `setStatus(int status)` 方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
1. **从地址栏显示来说**
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2. **从数据共享来说**
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3. **从运用地方来说**
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等
4. 从效率来说
forward:高.
redirect:低.
##### Servlet与线程安全
**Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。** 解决的办法是尽量不要定义name属性,而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized可以解决问题,但是会造成线程的等待,不是很科学的办法。
注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。
##### Request对象的主要方法有哪些
- setAttribute(String name,Object):设置名字为name的request 的参数值
- getAttribute(String name):返回由name指定的属性值
- getAttributeNames():返回request 对象所有属性的名字集合,结果是一个枚举的实例
- getCookies():返回客户端的所有 Cookie 对象,结果是一个Cookie 数组
- getCharacterEncoding() :返回请求中的字符编码方式 = getContentLength() :返回请求的 Body的长度
- getHeader(String name) :获得HTTP协议定义的文件头信息
- getHeaders(String name) :返回指定名字的request Header 的所有值,结果是一个枚举的实例
- getHeaderNames() :返回所以request Header 的名字,结果是一个枚举的实例
- getInputStream() :返回请求的输入流,用于获得请求中的数据
- getMethod() :获得客户端向服务器端传送数据的方法
- getParameter(String name) :获得客户端传送给服务器端的有 name指定的参数值
- getParameterNames() :获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例
- getParameterValues(String name):获得有name指定的参数的所有值
- getProtocol():获取客户端向服务器端传送数据所依据的协议名称
- getQueryString() :获得查询字符串
- getRequestURI() :获取发出请求字符串的客户端地址
- getRemoteAddr():获取客户端的 IP 地址
- getRemoteHost() :获取客户端的名字
- getSession() :返回和请求相关 Session
- getServerName() :获取服务器的名字
- getServletPath():获取客户端所请求的脚本文件的路径
- getServerPort():获取服务器的端口号
- removeAttribute(String name):删除请求中的一个属性
##### request.getAttribute()和 request.getParameter()有何区别
**从获取方向来看:**
getParameter()是获取 POST/GET 传递的参数值;
getAttribute()是获取对象容器中的数据值;
**从用途来看:**
getParameter用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。
getAttribute用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了
mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。
另外,可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。
setAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。
**总结:**
getParameter 返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等)
getAttribute 返回的是Object,需进行转换,可用setAttribute 设置成任意对象,使用很灵活,可随时用
#### 其他
##### 如果没有Cookie的话Session还能用吗?
可以用,一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID的方案的话, 如果客户端禁用了Cookie,那么Seesion就无法正常工作。
但是我可以将session保存到请求头或者local storage、sessionStorage 里面。
页:
[1]