一. Java语言有哪些特点
简单易学:Java有丰富的类库,不用手写轮子
面向对象:Java是一门面向对象的语言,支持封装、继承、多态
跨平台性:Java程序可以在不同的操作系统和硬件平台上运行,实现了
一次编写,到处运行的目标
安全性:Java具有高度的安全性,提供了注入类加载器、安全管理器和异常处理机制等安全机制
Java类加载器采用双亲委派模式,即在加载类时先从父类加载器中查找对应的类,如果父类加载器中没有找到,则再去子类加载器中查找。这种机制可以防止对Java核心类库的篡改,并确保应用程序使用的是正确的类。如果你自己手写了一个Object类,这个手写的Object类是不会被加载的,而是会使用Java提供的Object类
如果你就是想用自己写的Object类,那么需要自定义类加载器,重写其findClass方法
多线程:Java语言支持多线程编程,可以方便的实现并发操作
开放性:Java是一种开放性语言,具有开放的标准和规范,可以与其他语言进行交互和集成
高性能:Java的性能不断提高,特别是JIT编译器的引用使得Java程序的性能可以与C++等编译型语言媲美
当JIT编译器发现某个方法被频繁调用时,它会将该方法的字节码转换为本地机器码来提高执行速度。这是因为字节码是一种跨平台的中间代码,其性能较低,而本地机器码是针对特定硬件平台的机器指令,其性能更高。
二. 面向对象和面向过程的区别
面向过程
:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用时调用即可。性能较高,单片机、嵌入式开发一般采用面向过程开发面向对象
:是把构成问题的事物分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装
、继承
、多态
的特性,所以易维护
、易复用
、易扩展
。可以设计出低耦合的系统,但是从性能上来说,要比面向过程要低。
三. 八种基本数据类型的大小,以及他们的封装类
int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。
基本数据类型在声明时,系统会自动给它分配空间,而引用类型声明也只是分配了引用空间,必须通过实例化开辟数据空间后才可以赋值。
数组对象也是一个引用对象,将一个数组赋值给另一个数组时,只是复制了一个引用,所以通过某一个数组所做的修改,在另一个数组中也看得见。
public class Main {
public static void main(String[] args) {
int[] arr1 = {1, 1, 4, 5, 1, 4};
int[] arr2 = arr1;
arr2[0] = 0;
/**
* 输出结果是一样的,都为 0 1 4 5 1 4
*/
for (int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i] + " ");
}
System.out.println();
for (int i = 0; i < arr2.length; i++) {
System.out.print(arr2[i] + " ");
}
System.out.println();
/**
* 它们的引用地址也是一样的
*/
System.out.println(arr1);
System.out.println(arr2);
}
}
虽然Java语言中定义了boolean类型,但是在Java虚拟机中,没有专门的字节码指令用于处理boolean类型的值。相反,编译器将boolean类型的值编译成Java虚拟机中的int类型,其中0表示false,非0表示true。同样,boolean类型的数组在Java虚拟机中被编码为byte类型的数组。这是因为Java虚拟机的设计者们认为,使用int类型来代替boolean类型,不会对性能造成太大的影响,而且可以简化虚拟机的实现。
四. 标识符的命名规则
标识符的含义:
在程序中,我们自定义的内容,例如类的名字、方法名称、变量名称等,都是标识符命名规则:
标识符可以包含英文字母、0-9
的数字、$
以及_
,标识符不能以数字开头,不能是关键字命名规范:
类名首字母大写,驼峰命名法。变量名、方法名首字母小写,后续也是驼峰命名
五. instanceof关键字的作用
instanceof严格来说是Java中的一个双目运算符,用来测试一个对象是否为另一个对象的实例,用法如下
boolean result = obj instance Class
其中obj为一个对象,Class表示一个类或者一个接口,当obj为Class对象,或为其子类、实现类,结果返回true,否则返回false
注意:编译器会检查obj是否能转换为右边class类型,如果不能转换则直接报错
int i = 1;
boolean res = i instanceof Integer; // 编译不通过:不可转换的类型;无法将 'int' 转换为 'java.lang.Integer'
Integer i = new Integer(1);
boolean res = i instanceof Integer; // true
JavaSE规范中对instanceof运算符的规定是:如果obj为null,那么返回结果总为false
boolean res = null instanceof Integer;
六. Java自动装箱与拆箱
装箱就是自动将基本数据类型转换为包装类型(int -> Integer);底层调用的是Integer的valueOf(int)方法
int i = 10;
Integer i = Integer.valueOf(10);
拆箱就是自动将包装类型转换为基本数据类型(Integer -> int);底层调用的是intValue()方法
Integer i = Integer.valueOf(10);
int j = i.valueOf(i);
面试题1:
下面的代码会输出什么?
public class Tmp {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b);
System.out.println(c == d);
}
}
运行结果
true
false
为什么会出现这样的结果呢?输出表明a和b指向的是同一个对象,而c和d指向的不是同一个对象,我们来看一下Integer.valueOf()方法的底层源码
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
从注释中我们可以看到,此方法将始终缓存-128到127之间的值。
也就是如果数值在-128和127之间,就会返回IntegerCache.cache中已经存在的对象的引用,否则创建一个新的Integer对象。所以上面的代码中,a和b的数值为100,就是从缓存中取的已存在的对象,指向的是同一个对象,所以返回true;而c和d的值为200,并不在缓存中,所以是新建的Integer对象,所以返回false
七. 重载和重写的区别
重载(Overload):
是指在一个类中定义多个方法,它们具有相同的名称,但具有不同的参数列表(个数、类型、顺序),一边在不同的情况下可以调用不同的方法,重载方法可以在一个类中定义,也可以在不同类种定义,只要它们的方法签名不同即可例如下面的代码定义了一个名为sum的重载方法,它可以接收不同类型和数量的参数
public class MathUtils {
public static int sum(int a, int b) {
return a + b;
}
public static double sum(double a, double b) {
return a + b;
}
public static int sum(int a, int b, int c) {
return a + b + c;
}
}
重写(Override)
:是指在子类中重新定义(覆盖)父类中已有的方法,以便实现不同的功能或适应不同的需求。重写方法必须和父类中的方法具有相同的方法名称、参数列表和返回值类型,并且访问权限不能比父类中的方法更严格
public class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
通过重写方法,我们可以在子类中实现特定的功能,同时也可以保留父类中的方法实现。重写方法通常用于实现多态性和集成特性。
八. equals和==的区别
==
比较两个对象的引用是否相同,也就是比较它们在内存中的地址是否相同,如果两个对象的引用相同,则返回true,否则返回false例如下面代码中创建两个String类型的对象,他们的值相同但是引用不同,使用
==
比较会返回false
public class Tmp {
public static void main(String[] args) {
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
}
}
equals
是比较两个对象的内容是否相同,也就是比较它们的值是否相同。如果两个对象的内容相同,则返回true,否则返回false。在Java中,Object类的equals()方法默认实现是使用==
比较两个对象的引用,但可以在子类中重写该方法以实现比较对象内容的功能。例如下面的代码中创建了两个String类型的对象,它们的值相同,所以使用
equals
比较会返回true
public class Tmp {
public static void main(String[] args) {
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a.equals(b));
}
}
九. hashCode的作用
Java
的集合有两类,一类是List
,另一类是Set
。前者有序可重复,后者无序不可重复。当我们在Set
中插入的时候,如何判断已经存在该元素了呢?可以通过
equals
方法来判断,但是如果元素太多,用这样的方法就会比较慢
于是就有人发明了哈希算法来提高集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域
hashCode
可以这样理解:它返回的是根据对象内存地址换算出的一个值。这样一来,当set需要添加新元素时,先调用这个元素的hashCode
方法,就能一下子定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不需要再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals
方法与新元素进行比较,如果相同就不用存了,不相同就散列其他的地址。这样一来实际调用equals
方法的次数就大大降低了。
十. String、StringBuilder、StringBuffer的区别
String是只读字符串,并不是基本数据类型,而是一个对象,从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改,每次对String的操作都会生成新的String对象
每次
+
操作:隐式在堆上new
了一个跟原字符串相同的StringBuilder
对象,再调用append方法,拼接+
后面的字符
StringBuffer和StringBuilder都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到
他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuilder和StringBuffer来进行操作。
另外StringBuffer对方法加了同步锁或者对调用的方法加了同步锁(底层源码方法都加了synchronized),所以线程是安全的。
public synchronized StringBuffer append(StringBuffer sb) {
this.toStringCache = null;
super.append(sb);
return this;
}
StringBuilder没有对方法进行加同步锁,所以是非线程安全的
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
十一. ArrayList和LinkedList的区别
内部实现:
ArrayList是基于动态数组实现的,内部使用Object[]数组来存储元素。而LinkedList是基于双向链表实现的,内部使用Node节点来存储元素插入和删除操作:
ArrayList对于中间位置的插入和删除需要移动元素,因为它的底层是数组,需要将后面的元素往后移动,而LinkedList只需要修改节点的指针即可。因此,LinkedList在插入和删除操作方面比ArrayList效率更高随机访问:
由于ArrayList的底层是数组,所以可以根据下标快速随机访问元素,时间复杂度为O(1);而LinkedList是基于链表实现的,不能直接根据下标访问元素,需要从头或者从尾遍历到指定位置,时间复杂度为O(n)。内存占用:
由于LinkedList的每个元素都需要一个额外指针来指向下一个节点,因此占用的内存空间会比ArrayList多
十二. HashMap和Hashtable的区别
二者父类不同:
HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了Map、Cloneable、Serializable这三个接口
线程安全:
Hashtable是线程安全的,它的所有方法都是同步的(所有方法都用synchronized修饰),即对于多个线程同时访问一个Hashtable实例时,可以保证数据的唯一性。而HashMap不是线程安全的,如果多个线程同时访问一个HashMap实例,可能会出现数据不一致的情况null键和null值的支持:
Hashtable不允许键或值为null,否则会抛出NullPointerException异常;而HashMap可以允许null键和null值初始容量和负载因子:
Hashtable的初始容量和负载因子是固定的,在创建Hashtable实例时必须指定;而HashMap可以在创建时指定初始容量和负载因子,也可以在运行时动态调整性能:
因为Hashtable是线程安全的,因此在多线程环境下使用时,会存在一定的性能问题;而HashMap不是线程安全的,在单线程环境下使用时,性能要比Hashtable高
十三. Collection包结构,与Collections的区别
Java中的Collection包含了一组接口,用于表示一组对象的集合。它提供了一些通用的操作,如添加、删除、遍历等。Collection包中的主要接口有
List:
有序集合,可以有重复元素Set:
无序集合,不允许有重复元素Queue:
队列,通常用于实现先进先出(FIFO)的数据结构Deque:
双端队列,可以在队头或队尾进行插入和删除操作
Collections是Java中的一个工具类,它包含了一组静态方法,用于操作各种集合类型。它提供了一些常用的算法和工具方法,如排序、查找、复制等。Collections类中的方法通常是针对Collection类型的实例进行操作的
简单来说,Collection是一组结构,定义了集合的基本操作和属性,而Collections是一个工具类,提供了一些常用的算法和工具方法,用于操作各种集合类型的实例
需要注意的是:Collection和Collections之间是没有继承或实现关系的,它们是两个独立的概念
十四. Java的四种引用,强弱软虚
在Java中有四种类型的引用:强引用、软引用、弱引用、虚引用
强引用(Strong Reference):
是最常见的引用类型,它指向一个对象,只要强引用存在,垃圾回收器就不会回收该对象,可以通过new操作符、赋值操作符或方法调用等方式创建强引用
String str = new String("Hello");
软引用(Soft Reference):
是一种比较灵活的引用类型,它用来描述一些还有用,但是非必须的对象。只有当内存不足时,才会回收这些对象,可以通过SoftReference类创建软引用
SoftReference<String> str = new SoftReference<String>(new String("Hello"));
弱引用(Weak Reference):
比弱引用还要弱一些,它指向的对象只要没有强引用指向它时,就会被回收。可以通过WeakReference类创建弱引用
WeakReference<String> str = new WeakReference<>(new String("Hello"));
虚引用(Phantom Reference):
虚引用的回收机制与弱引用差不多,但是它在被回收之前,会被放入ReferenceQueue中。而其他引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制的存在,虚引用大多是被用于引用销毁前的处理工作。同时,虚引用在创建时,必须带有ReferenceQueue
PhantomReference<String> str = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
十五. 泛型常用特点
Java中的泛型是一种类型参数化机制,它可以让代码更加灵活、可读性更强,同时也可以提高代码的安全性和可维护性。泛型的常用特点包括
类型安全:
泛型可以让编译器在编译时就检查类型是否匹配,从而避免了很多类型转换和运行时错误可重用性:
泛型可以让同一个类或方法适用于不同的数据类型,从而提高了代码的可重用性可读性:
泛型可以让代码更易读,因为它可以让代码更具有表现力和可理解性性能优化:
泛型可以让代码更加高效,因为它可以避免在运行时进行类型转换,从俄提高了程序的性能
注意:Java中的泛型是在编译时实现的,而不是在运行时实现的。在编译时,Java编译器会进行类型擦除(Type Erasure),将泛型类型转换为普通的类型。因此,在运行时无法获取泛型类型的具体信息