一. Java语言有哪些特点

  1. 简单易学:Java有丰富的类库,不用手写轮子

  2. 面向对象:Java是一门面向对象的语言,支持封装、继承、多态

  3. 跨平台性:Java程序可以在不同的操作系统和硬件平台上运行,实现了一次编写,到处运行的目标

  4. 安全性:Java具有高度的安全性,提供了注入类加载器、安全管理器和异常处理机制等安全机制

    • Java类加载器采用双亲委派模式,即在加载类时先从父类加载器中查找对应的类,如果父类加载器中没有找到,则再去子类加载器中查找。这种机制可以防止对Java核心类库的篡改,并确保应用程序使用的是正确的类。如果你自己手写了一个Object类,这个手写的Object类是不会被加载的,而是会使用Java提供的Object类

    • 如果你就是想用自己写的Object类,那么需要自定义类加载器,重写其findClass方法

  5. 多线程:Java语言支持多线程编程,可以方便的实现并发操作

  6. 开放性:Java是一种开放性语言,具有开放的标准和规范,可以与其他语言进行交互和集成

  7. 高性能:Java的性能不断提高,特别是JIT编译器的引用使得Java程序的性能可以与C++等编译型语言媲美

    • 当JIT编译器发现某个方法被频繁调用时,它会将该方法的字节码转换为本地机器码来提高执行速度。这是因为字节码是一种跨平台的中间代码,其性能较低,而本地机器码是针对特定硬件平台的机器指令,其性能更高。

二. 面向对象和面向过程的区别

  • 面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用时调用即可。性能较高,单片机、嵌入式开发一般采用面向过程开发

  • 面向对象:是把构成问题的事物分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装继承多态的特性,所以易维护易复用易扩展。可以设计出低耦合的系统,但是从性能上来说,要比面向过程要低。

三. 八种基本数据类型的大小,以及他们的封装类

数据类型

大小(字节)

封装类

byte

1

Byte

short

2

Short

int

4

Integer

long

8

Long

float

4

Float

double

8

Double

char

2

Character

boolean

1

Boolean

  1. int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。

  2. 基本数据类型在声明时,系统会自动给它分配空间,而引用类型声明也只是分配了引用空间,必须通过实例化开辟数据空间后才可以赋值。

  3. 数组对象也是一个引用对象,将一个数组赋值给另一个数组时,只是复制了一个引用,所以通过某一个数组所做的修改,在另一个数组中也看得见。

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);
    }
}
  1. 虽然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的区别

  1. 内部实现:ArrayList是基于动态数组实现的,内部使用Object[]数组来存储元素。而LinkedList是基于双向链表实现的,内部使用Node节点来存储元素

  2. 插入和删除操作:ArrayList对于中间位置的插入和删除需要移动元素,因为它的底层是数组,需要将后面的元素往后移动,而LinkedList只需要修改节点的指针即可。因此,LinkedList在插入和删除操作方面比ArrayList效率更高

  3. 随机访问:由于ArrayList的底层是数组,所以可以根据下标快速随机访问元素,时间复杂度为O(1);而LinkedList是基于链表实现的,不能直接根据下标访问元素,需要从头或者从尾遍历到指定位置,时间复杂度为O(n)。

  4. 内存占用:由于LinkedList的每个元素都需要一个额外指针来指向下一个节点,因此占用的内存空间会比ArrayList多

十二. HashMap和Hashtable的区别

  1. 二者父类不同:HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了Map、Cloneable、Serializable这三个接口

  1. 线程安全:Hashtable是线程安全的,它的所有方法都是同步的(所有方法都用synchronized修饰),即对于多个线程同时访问一个Hashtable实例时,可以保证数据的唯一性。而HashMap不是线程安全的,如果多个线程同时访问一个HashMap实例,可能会出现数据不一致的情况

  2. null键和null值的支持:Hashtable不允许键或值为null,否则会抛出NullPointerException异常;而HashMap可以允许null键和null值

  3. 初始容量和负载因子:Hashtable的初始容量和负载因子是固定的,在创建Hashtable实例时必须指定;而HashMap可以在创建时指定初始容量和负载因子,也可以在运行时动态调整

  4. 性能:因为Hashtable是线程安全的,因此在多线程环境下使用时,会存在一定的性能问题;而HashMap不是线程安全的,在单线程环境下使用时,性能要比Hashtable高

十三. Collection包结构,与Collections的区别

  • Java中的Collection包含了一组接口,用于表示一组对象的集合。它提供了一些通用的操作,如添加、删除、遍历等。Collection包中的主要接口有

    1. List:有序集合,可以有重复元素

    2. Set:无序集合,不允许有重复元素

    3. Queue:队列,通常用于实现先进先出(FIFO)的数据结构

    4. Deque:双端队列,可以在队头或队尾进行插入和删除操作

  • Collections是Java中的一个工具类,它包含了一组静态方法,用于操作各种集合类型。它提供了一些常用的算法和工具方法,如排序、查找、复制等。Collections类中的方法通常是针对Collection类型的实例进行操作的

  • 简单来说,Collection是一组结构,定义了集合的基本操作和属性,而Collections是一个工具类,提供了一些常用的算法和工具方法,用于操作各种集合类型的实例

  • 需要注意的是:Collection和Collections之间是没有继承或实现关系的,它们是两个独立的概念

十四. Java的四种引用,强弱软虚

  • 在Java中有四种类型的引用:强引用、软引用、弱引用、虚引用

    1. 强引用(Strong Reference):是最常见的引用类型,它指向一个对象,只要强引用存在,垃圾回收器就不会回收该对象,可以通过new操作符、赋值操作符或方法调用等方式创建强引用

    String str = new String("Hello");
    1. 软引用(Soft Reference):是一种比较灵活的引用类型,它用来描述一些还有用,但是非必须的对象。只有当内存不足时,才会回收这些对象,可以通过SoftReference类创建软引用

    SoftReference<String> str = new SoftReference<String>(new String("Hello"));
    1. 弱引用(Weak Reference):比弱引用还要弱一些,它指向的对象只要没有强引用指向它时,就会被回收。可以通过WeakReference类创建弱引用

    WeakReference<String> str = new WeakReference<>(new String("Hello"));
    1. 虚引用(Phantom Reference):虚引用的回收机制与弱引用差不多,但是它在被回收之前,会被放入ReferenceQueue中。而其他引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制的存在,虚引用大多是被用于引用销毁前的处理工作。同时,虚引用在创建时,必须带有ReferenceQueue

    PhantomReference<String> str = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());

十五. 泛型常用特点

  • Java中的泛型是一种类型参数化机制,它可以让代码更加灵活、可读性更强,同时也可以提高代码的安全性和可维护性。泛型的常用特点包括

    1. 类型安全:泛型可以让编译器在编译时就检查类型是否匹配,从而避免了很多类型转换和运行时错误

    2. 可重用性:泛型可以让同一个类或方法适用于不同的数据类型,从而提高了代码的可重用性

    3. 可读性:泛型可以让代码更易读,因为它可以让代码更具有表现力和可理解性

    4. 性能优化:泛型可以让代码更加高效,因为它可以避免在运行时进行类型转换,从俄提高了程序的性能

  • 注意:Java中的泛型是在编译时实现的,而不是在运行时实现的。在编译时,Java编译器会进行类型擦除(Type Erasure),将泛型类型转换为普通的类型。因此,在运行时无法获取泛型类型的具体信息

十六. Java创建对象有几种方式