1.基础概念
1.1Java特点
- 简单易学
- 面向对象(封装,继承,多态)
- 平台无关性( Java 虚拟机实现平台无关性,**.java文件–>.class–>二进制文件**)
- 支持多线程
- 可靠性(具备异常处理和自动内存管理机制)
- 安全性(Java 语言本身的设计就提供了多重安全防护机制如访问权限修饰符、限制程序直接访问操作系统资源)
- 。。。
1.2 JVM vs JDK vs JRE
1.2.1 JVM
Java虚拟机(JVM)用于运行Java字节码,JVM是平台有关的,即它需要保证使用相同字节码在不同操作系统上得到相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
1.2.2 JDK 和 JRE
JDK(Java Development Kit)能够创建和编译 Java 程序。其中包含了 JRE,同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控⼯具)、javap(反编译工具)等等。
JRE(Java Runtime Environment) 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。
JDK包含JRE!!!
1.3 什么是字节码?采用字节码的好处是什么?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C++,Rust,Go 等语言还是有一定差距的),而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
1.4 为什么说 Java 语言“编译与解释并存”?
高级编程语言主要分为两种:
- 编译型:直接将源码编译为机器码,一步到位。这种方式执行速度较快,但是开发效率较低。代表性语言有C、C++、Go、Rust 等等。
- 解释型:需要通过解释器将源码解释为机器码。这种方式执行速度较慢,但是开发效率高。代表性语言有Python、JavaScript、PHP 等等。
而Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行。
1.5 Java 和 C++ 的区别?
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承
- Java 有自动内存管理垃圾回收机制(GC),不需要手动释放无用内存
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载
2. 基本语法
2.1 标识符和关键字的区别是什么?
标识符就是一个名字,关键字是有特殊含义的标识符。
2.2 Java关键字
2.3 自增自减运算符
符号在前就先加/减,后赋值;符号在后就先赋值,后加/减。
2.4 移位运算符
- << :左移运算符,向左移若干位,高位丢弃,低位补零。x << 1,相当于 x 乘以2(不溢出的情况下)。
- >> :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1,相当于 x 除以 2。
- >>> :无符号右移,忽略符号位,空位都以 0 补齐。
2.5 基本类型和包装类型的区别?
-用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
- 存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
- 占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
- 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 比较方式:对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。
2.6 自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来
- 拆箱:将包装类型转换为基本数据类型
Integer i = 10; //装箱
int n = i; //拆箱
3. 变量
3.1 成员变量与局部变量的区别?
- 语法形式:
- 成员变量属于类,局部变量是代码块或方法中定义的变量或是方法的参数
- 成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰
- 成员变量和局部变量都能被 final 所修饰
- 存储方式:
- 如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的
- 如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存
- 生存时间:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡
- 默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值
3.2 静态变量的作用
静态变量也就是被 static修饰的变量。它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量。也就是说,静态变量只会被分配一次内存,即使创建多个对象,这样可以节省内存。
3.3 字符型常量和字符串常量的区别?
- 形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符。
- 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。
- 占内存大小:字符常量只占 2 个字节; 字符串常量占若干个字节。
4. 方法
4.1 静态方法为什么不能调用非静态成员?
静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
4.2 静态方法和实例方法有何不同?
- 调用方式
在外部调用静态方法的时候,可以使用类名.方法名(推荐使用)或实例名.方法名的方式;而调用实例方法只能使用实例名.方法名的方式。所以使用静态方法的时候无需创建对象。
- 访问类成员是否存在限制
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
4.3 重载和重写有什么区别?
- 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理。
- 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,就要覆盖父类方法。
5.基本数据类型
5.1 基本类型和包装类型的区别
- 成员变量包装类型不赋值就是
null
,而基本类型有默认值且不是null。
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被
static
修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。 - 比于对象类型, 基本数据类型占用的空间非常小。
6. 面向对象基础
6.1 面向对象和面向过程的区别
- 面向过程把解决问题的过程拆解成方法,通过一个个方法去解决问题。
- 面向对象先抽象出对象,用对象去执行方法解决问题。
面向对象相较于面向过程更易维护、易复用、易扩展。
6.2 对象实体与对象引用有何不同?
- 对象实例存放在内存中,对象引用存放在栈内存中。
- 一个对象引用可以指向0个或1个对象。
- 一个对象实例可以被n个引用指向。
6.3 对象的相等和引用相等的区别
- 对象相等一般比较的是内存中存放的内容是否相等。
- 引用相等一般比较的是它们指向的内存地址是否相等。
6.4 如果一个类没有声明构造方法,该程序能正确执行吗?
如果一个类没有声明构造方法,也可以执行,因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
6.5 构造方法有哪些特点?是否可被 override?
构造方法的特点:
- 方法名与类名相同
- 没有返回值,且不能用void声明
- 生成类的对象时自动执行,无需调用
构造方法不能被重写,但是可以重载。
6.6 面向对象三大特征
- 封装
- 继承
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法
- 多态:一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定
- 多态不能调用“只在子类存在但在父类不存在”的方法
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法
6.7 接口和抽象类有什么共同点和区别?
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法
区别:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。即抽象类里面定义的都是一个继承体系中的共性内容;
接口是功能的集合,是一个体系额外的功能,是暴露出来的规则。 - 一个类只能继承一个类,但是可以实现多个接口
- 接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
6.8 深拷贝和浅拷贝区别了解吗?
- 浅拷贝 :浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
- 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
7. Object
7.1 ==和equals()的区别
==
- 对于基本数据类型,==比较的是值
- 对于引用数据类型,==比较的是对象的内存地址
euqals()
- euqals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等
- euqals()方法存在于
Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法
euqals()方法存在两种使用情况:
- 类没有重写euqals()方法:通过
equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法
- 类重写了
equals()
方法 :一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
7.2 为什么要有hashCode?
hashCode能够提高程序执行速度,但是实质上hashCode()和euqals()都是用于比较两个对象是否相等。
那为什么不只使用hashCode()呢?
因为两个对象的hashCode值相等并不代表两个对象就一定相等,因为哈希算法有可能刚好让多个对象传回相同的哈希值,这也称为哈希碰撞。因此我们一般比较对象相等时先hashCode()减少equals的使用次数,再euqals()确保两个对象真的相等。
8.String
8.1 String、StringBuffer、StringBuilder 的区别?
可变性
- String是不可变的
- StringBuilder与StringBuffer都是继承自AbstractStringBuilder类,该类中提供了很多修改字符串的方法
线程安全性
- String中的对象不可变(常量),线程安全
- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
- StringBuilder并没有对方法进行加同步锁,所以是非线程安全的
性能
改变String对象时,都会生成新的String对象
StringBuffer的改变针对自身
StringBuilder相比StringBuffer有10%~15% 左右的性能提升,但存在多线程不安全的风险
总结:
- 少量数据用String
- 单线程操作大量数据用StringBuilder
- 多线程操作大量数据用StringBuffer
8.2 字符串拼接用“+” 还是 StringBuilder?
Java中支持以”+”来拼接字符串,但是本质上还是通过StringBuilder调用append()方法实现的。如果简单的拼接两者区别并不大,但是如果要在循环内拼接字符串的话:建议使用StringBuilder来拼接字符串,因为每使用一次”+”都会生成一个新的StringBuilder对象,这样会导致创建过多的StringBuilder对象。
8.3 String和Object的equals()方法有何区别?
String中的equals被重写过,用于比较String对象的字符串值是否相等;Object的equals比较的是对象的内存地址。
9. 泛型
9.1 什么是泛型?为什么要使用泛型?
泛型,即参数化类型,就是将原来具体的类型参数化,在不创建新类型的情况下,通过泛型制定不同的类型来控制形参具体限制的类型。
9.2 泛型的使用方式有哪几种?
泛型类、泛型接口、泛型方法
- 泛型类:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
实例化泛型
Generic<Integer> genericInteger = new Generic<Integer>(123456);
- 泛型接口
public interface Generator<T> {
public T method();
}
实现泛型接口,不指定类型
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
实现泛型接口,不指定类型
- 泛型方法
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
使用
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
10. 反射
10.1 何为反射?
反射可以获取并调用任意一个类的所有属性和方法。
10.2 反射的优缺点
- 优点:反射能够提高代码的灵活性
- 缺点:存在安全问题(无视泛型参数的安全检查);性能稍差。不过这些影响都不大!
11. 注解
11.1 何为注解?
注解可以看作是一种特殊的注释,主要用于修饰类、方法或变量,提供某些信息供程序在编译或者运行时使用。