Java 概述
1. 什么是 Java ?
Java 是一门面向对象的编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此Java 语言具有功能强大和简单易用两个特征。
Java 语言作为静态面向对象编程语言的优秀代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
- 面向对象(封装,继承,多态);
- 平台无关性,平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。
- 支持多线程。C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持;
- 编译与解释并存;
3.JVM、JDK 和 JRE 有什么区别?
JVM : Java Virtual Machine , Java 虚拟机, Java 程序运行在 Java 虚拟机上。针对不同系统的实现(Windows , Linux , macOS )不同的 JVM ,因此 Java 语言可以实现跨平台。
JRE : Java 运 ⾏ 时环境。它是运 ⾏ 已编译 Java 程序所需的所有内容的集合,包括Java 虚拟机( JVM ), Java 类库, Java 命令和其他的 ⼀ 些基础构件。但是,它不能⽤于创建新程序。
JDK : Java Development Kit ,它是功能 ⻬ 全的 Java SDK 。它拥有 JRE 所拥有的 ⼀切,还有编译器(javac )和 ⼯ 具(如 javadoc 和 jdb )。它能够创建和编译程序。简单来说,JDK 包含 JRE , JRE 包含 JVM 。
4.说说什么是跨平台性?原理是什么?
所谓跨平台性,是指 Java 语言编写的程序,一次编译后,可以在多个系统平台上运行。
实现原理: Java 程序是通过 Java 虚拟机在系统平台上运行的,只要该系统可以安装相应的Java 虚拟机,该系统就可以运行 java程序。
5. 什么是字节码?采用字节码的好处是什么 ?
所谓的字节码,就是 Java 程序经过编译之类产生的 .class 文件,字节码能够被虚拟机识别,从而实现Java 程序的跨平台性。
Java 程序从源代码到运行主要有三步:
- 编译 :将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
- 解释 :虚拟机执行Java字节码,将字节码翻译成机器能识别的机器码
- 执行 :对应的机器执行二进制机器码
只需要把 Java 程序编译成 Java 虚拟机能识别的 Java 字节码,不同的平台安装对应的Java虚拟机,这样就可以可以实现 Java 语言的平台无关性。
6. 为什么说 Java 语言 “ 编译与解释并存 ” ?
高级编程语言按照程序的执行方式分为 编译型 和 解释型 两种。 简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。 比如,你想读一本外国的小说,你可以找一个翻译人员帮助你翻译,有两种选择方式,你可以先等翻译人员将全本的小说(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。 Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码( \*.class 文件),这种字节码必须再经过 JVM ,解释成操作系统能识别的机器码,在由操作系统执行。因此,我们可以认为 Java 语言 编译 与 解释 并存。
7.Java有哪些数据类型?
定义: Java 语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。Java语言数据类型分为两种: 基本数据类型 和 引用数据类型 。
基本数据类型:
- 数值型
- 整数类型(byte、short、long)
- 浮点类型(float、double)
- 字符型(char)
- 布尔型(boolean)
Java 基本数据类型范围和默认值:
引用数据类型:
- 类(class)
- 接口(interface)
- 数组([])
8.自动类型转换、强制类型转换?看看这几行代码?
Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
这就好像,小杯里的水倒进大杯没问题,但大杯的水倒进小杯就不行了,可能会溢出。
float f=3.4 ,对吗?
不正确。 3.4 是单精度数,将双精度型( double )赋值给浮点型( float )属于下转型(down-casting ,也称为窄化)会造成精度损失,因此需要强制类型转换 float f = (float)3.4; 或者写成 float f =3.4F
short s1 = 1; s1 = s1 + 1 ; 对吗? short s1 = 1 ; s1 += 1 ; 对吗?
对于 short s1 = 1; s1 = s1 + 1; 编译出错,由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。
而 short s1 = 1; s1 += 1; 可以正确编译,因为 s1+= 1; 相当于 s1 = (short(s1 + 1); 其中有隐含的强制类型转换。
9. 什么是自动拆箱 / 封箱?
- 装箱 :将基本类型用它们对应的引用类型包装起来;
- 拆箱 :将包装类型转换为基本数据类型;
Java可以自动对基本数据类型和它们的包装类进行装箱和拆箱。
举例:
Integer i = 10 ; // 装箱
int n = i; // 拆箱
10.& 和 && 有什么区别?
& 运算符有两种用法: 短路与 、 逻辑与 。
&& 运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true 。
&& 之所以称为短路运算是因为,如果 && 左边的表达式的值是 false ,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&& 而不是 & 。
例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为 username != null &&!username.equals("") ,二者的顺序不能交换,更不能用& 运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。
注意 :逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
11.switch 是否能作用在 byte/long/String上?
Java5 以前 switch(expr) 中, expr 只能是 byte 、 short 、 char 、 int 。
从 Java 5 开始, Java 中引入了枚举类型, expr 也可以是 enum 类型。
从 Java 7 开始, expr 还可以是字符串 (String) ,但是长整型 (long) 在目前所有的版本中都是不可以的
12.break ,continue ,return 的区别及作用?
- break 跳出整个循环,不再执行循环( 结束当前的循环体 )
- continue 跳出本次循环,继续执行下次循环( 结束正在执行的循环 进入下一个循环条件 )
- return 程序返回,不再执行下面的代码( 结束当前的方法 直接返回 )
13.用最有效率的方法计算2乘以8?
2 << 3。位运算,数字的二进制位左移三位相当于乘以2的三次方。
14.说说自增自减运算?看下这几个代码运行结果?
在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1 , Java提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++) 和自减运算符(- -)。 ++和 -- 运算符可以放在变量之前,也可以放在变量之后。当运算符放在变量之前时( 前缀 ) ,先自增 / 减,再赋值;当运算符放在变量之后时 ( 后缀) ,先赋值,再自增 / 减。
例如,当 b = ++a 时,先自增(自己增加 1 ),再赋值(赋值给 b );当 b = a++ 时,先赋值 ( 赋值给 b) ,再自增(自己增加 1 )。也就是, ++a 输出的是 a+1 的值,a++ 输出的是 a 值。
用一句口诀就是: “ 符号在前就先加 / 减,符号在后就后加 / 减 ” 。
答案是 1 。有点离谱对不对。
对于 JVM 而言,它对自增运算的处理,是会先定义一个临时变量来接收 i 的值,然后进行自增运算,最后又将临时变量赋给了值为2 的 i ,所以最后的结果为 1 。
相当于这样的代码:
这段代码会输出什么?
答案是 0 。
和上面的题目一样的道理,同样是用了临时变量, count 实际是等于临时变量的值。
15. ⾯ 向对象和 ⾯ 向过程的区别 ?
- ⾯向过程 :面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用就可以。
- ⾯向对象 :面向对象,把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事件在解决整个问题的过程所发生的行为。 目的是为了写出通用的代码,加强代码的重用,屏蔽差异性。
用一个比喻:面向过程是编年体;面向对象是纪传体。
16.面向对象有哪些特性?
封装把⼀ 个对象的属性私有化,同时提供 ⼀ 些可以被外界访问的属性的 ⽅ 法。
- 继承
继承是使⽤已存在的类的定义作为基础创建新的类,新类的定义可以增加新的属性或新的方法,也可以继承父类的属性和方法。通过继承可以很方便地进行代码复用
关于继承有以下三个要点:
1. ⼦ 类拥有 ⽗ 类对象所有的属性和 ⽅ 法(包括私有属性和私有 ⽅ 法),但是 ⽗ 类中的私有属性和⽅ 法 ⼦ 类是 ⽆ 法访问,只是拥有。
2. ⼦ 类可以拥有 ⾃⼰ 属性和 ⽅ 法,即 ⼦ 类可以对 ⽗ 类进 ⾏ 扩展。
3. ⼦ 类可以 ⽤⾃⼰ 的 ⽅ 式实现 ⽗ 类的 ⽅ 法
17. 重载( overload )和重写( override )的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
- 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
方法重载的规则:
1. 方法名一致,参数列表中参数的顺序,类型,个数不同。
2. 重载与方法的返回值无关,存在于父类和子类,同类中。
3. 可以抛出不同的异常,可以有不同修饰符。
18. 访问修饰符 public 、 private 、 protected 、以及不写(默认)时的区别?
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。 Java 支持 4 种不同的访问权限。
- default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。可以修 饰在类、接口、变量、方法。private : 在同一类内可见。可以修饰变量、方法。 注意:不能修饰类(外部 类)
- public : 对所有类可见。可以修饰类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。可以修饰变量、方法。 注意:不能修饰类(外部类)
19.this 关键字有什么作用?
this 是自身的一个对象,代表对象本身,可以理解为: 指向对象本身的一个指针 。
this 的用法在 Java 中大体可以分为 3 种:
1. 普通的直接引用, this 相当于是指向当前对象本身
2. 形参与成员变量名字重名,用 this 来区分:
3. 引用本类的构造函数
20.抽象类(abstract class)和接口(interface)有什么区别?
1. 接 ⼝ 的 ⽅ 法默认是 public ,所有 ⽅ 法在接 ⼝ 中不能有实现 (Java 8 开始接 ⼝⽅ 法可以有默认实现),⽽ 抽象类可以有 ⾮ 抽象的 ⽅ 法。
2. 接 ⼝ 中除了 static 、 final 变量,不能有其他变量, ⽽ 抽象类中则不 ⼀ 定。
3. ⼀ 个类可以实现多个接 ⼝ ,但只能实现 ⼀ 个抽象类。接 ⼝⾃⼰ 本身可以通过extends 关键字扩展多个接 ⼝ 。
4. 接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
总结 ⼀ 下 jdk7~jdk9 Java 中接 ⼝ 的变化:
1. 在 jdk 7 或更早版本中,接 ⼝⾥⾯ 只能有常量变量和抽象 ⽅ 法。这些接 ⼝⽅ 法必须由选择实现接⼝ 的类实现。
2. jdk 8 的时候接 ⼝ 可以有默认 ⽅ 法和静态 ⽅ 法功能。
3. jdk 9 在接 ⼝ 中引 ⼊ 了私有 ⽅ 法和私有静态 ⽅ 法。
21. 成员变量与局部变量的区别有哪些?
1. 从语法形式上看 :成员变量是属于类的, ⽽ 局部变量是在 ⽅ 法中定义的变量或是⽅ 法的参数;成员变量可以被 public , private , static 等修饰符所修饰, ⽽ 局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被final 所修饰。
2. 从变量在内存中的存储 ⽅ 式来看 :如果成员变量是使 ⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。 对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果 为引⽤ 数据类型,那存放的是指向堆内存对象的引 ⽤ 或者是指向常量池中的地址。
3. 从变量在内存中的 ⽣ 存时间上看 :成员变量是对象的 ⼀ 部分,它随着对象的创建⽽ 存在, ⽽ 局部变量随着 ⽅ 法的调 ⽤⽽⾃ 动消失。
4. 成员变量如果没有被赋初值 :则会 ⾃ 动以类型的默认值 ⽽ 赋值( ⼀ 种情况例外 :被 final 修饰的成员变量也必须显式地赋值), ⽽ 局部变量则不会 ⾃ 动赋值。
22. 静态变量和实例变量的区别?静态方法、实例方法呢?
静态变量和实例变量的区别?
静态变量 : 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。
实例变量 : 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
静态 ⽅ 法和实例 ⽅ 法有何不同 ?
类似地。
静态方法 : static 修饰的方法,也被称为类方法。在外部调 ⽤ 静态 ⽅ 法时,可以使⽤" 类名 . ⽅ 法名 " 的 ⽅ 式,也可以使 ⽤ " 对象名 . ⽅ 法名 " 的 ⽅ 式。静态方法里不能访问类的非静态成员变量和方法。
实例 ⽅ 法 :依存于类的实例,需要使用 " 对象名 . ⽅ 法名 " 的 ⽅ 式调用;可以访问类
的所有成员变量和方法。
24.final 关键字有什么作用?
final 表示不可变的意思,可用于修饰类、属性和方法:
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可变,被final修饰的变量必须被显式第指定初始值,还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
例如:
一张图说明:
25.final、finally、finalize的区别?
- final 用于修饰变量、方法和类:final修饰的类不可被继承;修饰的方法不可被重写;修饰的变量不可变。
- finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下, System.exit (0) 可以阻断 finally 执行。
- finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
26.==和 equals 的区别?
== : 它的作 ⽤ 是判断两个对象的地址是不是相等。即,判断两个对象是不是同 ⼀ 个对象( 基本数据类型 == 比较的是值,引 ⽤ 数据类型 == 比较的是内存地址 ) 。
equals() : 它的作 ⽤ 也是判断两个对象是否相等。但是这个 “ 相等 ” 一般也分两种情况:
- 默认情况:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时, 等价于通过“ == ”比较这两个对象,还是相当于比较内存地址。
- 自定义情况:类覆盖了 equals() ⽅法。我们平时覆盖的 equals()方法一般是比较两个对象的内容是否相同,自定义了一个相等的标准,也就是两个对象的值是否相等。
举个例⼦,Person,我们认为两个人的编号和姓名相同,就是一个人:
27.hashCode与 equals?
这个也是面试常问 ——“ 你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写hashCode ⽅ 法? ”
什么是HashCode?
hashCode() 的作 ⽤ 是获取哈希码,也称为散列码;它实际上是返回 ⼀ 个 int 整数,定义在 Object 类中, 是一个本地 ⽅ 法,这个 ⽅ 法通常 ⽤ 来将对象的内存地址转换为整数之后返回。
哈希码主要在哈希表这类集合映射的时候用到,哈希表存储的是键值对 (key-value) ,它的特点是:能根据“ 键 ” 快速的映射到对应的 “ 值 ” 。这其中就利 ⽤ 到了哈希码!
为什么要有 hashCode?
上面已经讲了,主要是在哈希表这种结构中用的到。例如HashMap怎么把key映射到对应的value上呢?用的就是哈希取余法,也就是拿哈希码和存储元素的数组的长度取余,获取key对应的value所在的下标位置。
为什么重写 quals 时必须重写 hashCode ⽅ 法?
如果两个对象相等,则 hashcode ⼀ 定也是相同的。两个对象相等,对两个对象分别调⽤ equals ⽅ 法都返回 true 。反之,两个对象有相同的 hashcode 值,它们也不 ⼀ 定是相等的 。因此, equals ⽅ 法被覆盖过,则 hashCode ⽅ 法也必须被覆盖。 hashCode() 的默认 ⾏ 为是对堆上的对象产 ⽣ 独特值。如果没有重写 hashCode() ,则该class 的两个对象 ⽆ 论如何都不会相等(即使这两个对象指向相同的数据)
为什么两个对象有相同的 hashcode 值,它们也不 ⼀ 定是相等的?
因为可能会 碰撞 , hashCode() 所使 ⽤ 的散列算法也许刚好会让多个对象传回相同的散列值。越糟糕的散列算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode )。
28.Java 是值传递,还是引用传递?
Java 语言是 值传递 。 Java 语言的方法调用只支持参数的值传递。当一个对象实例作 为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址。而对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传递过去,因此引用类型也是值传递。
29.深拷贝和浅拷贝?
- 浅拷贝 :仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。
- 深拷贝 :完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份
例如现在有一个order对象,里面有一个products列表,它的浅拷贝和深拷贝的示意图:
因此深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象。
浅拷贝如何实现呢?
Object 类提供的 clone() 方法可以非常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
- 重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
- 序列化:可以先讲原对象序列化,再反序列化成拷贝对象
30.Java 创建对象有哪几种方式?
Java 中有以下四种创建对象的方式 :
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
前两者都需要显式地调用构造方法。对于 clone 机制 , 需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在Java 中序列化可以通过实现Externalizable或者 Serializable 来实现。