Java知识点整理

1.1 Java基础

1.1.1 JDK和JRE和JVM的区别

JDK(Java Development Kit)是针对Java开发人员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。

JRE(Java Runtime Environment)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。

JVM(Java Virtual Machine)就是我们说的Java虚拟机,Java程序就运行在虚拟机中。

1.1.2 JAVA基本数据类型

JAVA基本数据类型共有8种,分为整数类型、浮点类型、字符类型和布尔类型:

  • 整数类型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
  • 浮点类型:float(4字节)、double(8字节)
  • 字符类型:char(2字节)
  • 布尔类型:boolean(1字节)

1.1.3 面向对象三大特性

  • 封装:把客观事物封装成抽象的类,将事物的特征功能抽取为类的属性和方法,供其他被允许的类调用,屏蔽了内部具体的实现细节,保证安全。
  • 继承:子类继承父类,可以继承父类中所定义的属性和方法。通过使用继承我们能够非常方便地复用以前的代码。
  • 多态:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。例如:List list = new ArrayList()。在Java中有两种形式可以实现多态:继承和实现。

1.1.4 创建对象的方式有哪些

  1. 使用new关键字调用构造方法直接创建对象。
  2. 使用反射创建对象,获取类的字节码对象,调用newInstance()方法创建。
  3. 如果一个类实现了Cloneable接口,还可以使用clone()方法克隆对象。
  4. 使用反序列化机制创建对象。

任何Java框架底层创建对象都离不开这几种机制,例如:Spring框架中通过工厂创建对象,工厂的本地还是构造方法和反射。

1.1.5 什么是多态机制

所谓多态就是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。例如:List list = new ArrayList()

Java实现多态有三个必要条件:继承、重写、向上转型:

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要父类的引用指向子类对象,只有这样该引用才能够既调用父类的方法也能调用子类的方法。

1.1.6 重载(Overload)和重写(Override)的区别

  • 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。
  • 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类。

1.1.7 接口和抽象类的区别,什么情况下使用接口什么情况下使用抽象类

接口和抽象类,最明显的区别就是接口只是定义了一些方法而已,在不考虑Java8中default方法情况下,接口中是没有实现的代码的。

抽象类中的抽象方法可以有publicprotecteddefault这些修饰符,而接口中默认修饰符是public,不可以使用其它修饰符。

接口和抽象类的职责不一样:

  • 接口主要用于制定规范,因为我们提倡也经常使用的都是面向接口编程。
  • 抽象类主要目的是为了复用,比较典型的就是模板方法模式。

所以当我们想要定义标准、规范的时候,就使用接口。当我们想要复用代码的时候,就使用抽象类。

一般在实际开发中,我们会先把接口暴露给外部,然后在业务代码中实现接口。如果多个实现类中有相同可复用的代码,则在接口和实现类中间加一层抽象类,将公用部分代码抽出到抽象类中。

1.1.8 什么是反射机制

JAVA反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射将类中的成员和属性等拆解出来形成新的对象,主要包括:

  • 字节码对象(Class)
  • 方法对象(Method)
  • 属性对象(Field)
  • 构造方法对象(Constructor)

对于一个私有的成员如果想要访问它,需要先设置setAccessible(true),这就是我们常说的暴力反射。

反射机制的优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。

反射机制的缺点:对性能有影响,这类操作总是慢于直接执行java代码。

1.1.9 说说你对Java中this和super关键字的理解

this关键字指向本类对象,本类内调用本类的其它成员属性和方法。

super指向父类,通过super调用父类的成员属性和方法。

通过super()this()方法可以调用父类和本类的构造方法。

在构造方法使用super()this()均需放在构造方法内第一行。

thissuper不能同时出现在一个构造函数里面。

1.1.10 说一说你对Java中static关键字的理解

static代表静态,它的主要意义是在于创建独立于具体对象的变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!

static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。

static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

1.1.11 break ,continue ,return 的区别及作用

  • break:跳出总上一层循环,不再执行循环(结束当前的循环体)。
  • continue:跳出本次循环,继续执行下次循环(结束正在执行的循环,进入下一个循环条件)。
  • return:程序返回,不再执行下面的代码(结束当前的方法,直接返回)。

1.1.12 Java中final关键字有什么作用

final代表最终的意思,final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。

需要注意的是:被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的。

例如:private final User user = new User();

user = new User("张三");这种赋值操作是不被允许的。

user.setName("张三");这种操作是运行的,没有改变指针引用,改变的是内容。

思考:抽象类能使用final修饰吗?不能,因为抽象类的目的是被继承,而final修饰的类不能被继承。

能否自定义一个类继承String类?不能,因为String类已经被final修饰。

1.1.13 final finally finalize区别

  • final:可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally:一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize:是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

调用了finalize方法,会马上清理内容吗?不会。

1.1.14 匿名内部类能否访问外部局部变量

匿名内部类访问外部局部变量需要再局部变量前加上final关键字,例如:

1
2
3
4
5
6
7
8
9
10
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}

因为生命周期不一致,局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

1.1.15 &和&&以及|和||的区别

  • &:逻辑与运算,需要判断&两边都为true,则才为true

  • &&:短路与运算,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算,相对于&少了一次运算效率更高。

  • |:逻辑或运算,需要判断|两边都为false,则才为false

  • ||:短路或运算,如果||左边的表达式的值是true,右边的表达式会被直接短路掉,不会进行运算,相对于|少了一次运算效率更高。

1.1.16 == 和 equals 的区别是什么

  • ==:可以比较基本数据类型也可以比较引用数据类型,比较基本数据类型是比较值是否相等,比较引用数据类型是比较引用地址是否相等(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。
  • equals():一般用于对象的比较,比较两个对象是否相等,会先调用对象的hashCode方法比较两个对象的hashCode是否相等,再调用equals()方法比较是否相等。

思考:

  1. 两个对象equals相等,那么hashCode值是否可以不相等?不可以。
  2. 两个对象hashCode值相等,那么两个对象是否一定相等?不一样。
  3. 重写一个类的equals方法有什么需要注意的?先重写hashCode方法,再重写equals

1.1.17 hashCode 方法的作用是什么

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。

这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode() 定义在JDK的Object类中,这就意味着 Java中的任何类都包含有hashCode()方法。

例如:我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的hashCodeHashSet会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals()方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新存储到其他位置。

1.1.18 String str=”i”与 String str=new String(“i”)一样吗

String str="i" 的方式,java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。

思考:String s = new String("xyz");创建了几个字符串对象?

答案:创建了两个字符串对象,一个在常量池中,一个在堆内存中。

1.1.19 String、StringBuffer、StringBuilder的区别是什么

  1. 可变性

    • String类中使用字符数组保存字符串,private final char value[],所以String对象是不可变的。
    • StringBuilderStringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
  2. 线程安全性

    • String中的对象是不可变的,也就可以理解为常量,线程安全。
    • StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
    • StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
  3. 性能

    • 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。
    • StringBufferStringBuilder每次都会对对象本身进行操作,而不是生成新的对象并改变对象引用,大量字符串操作的时候,相对而言StringBufferStringBuilder效率会更高,更节省内存资源。

1.1.20 Integer a= 127 与 Integer b = 127相等吗

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Integer a = 127 ;
Integer b = 127 ;
Integer c = 128;
Integer d = 128 ;

System.out.println(a==b); //true
System.out.println(c==d); //false
System.out.println(a.equals(b)); //true
System.out.println(c.equals(d)); //true
}

如果整型字面量的值在-128127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的 Integer对象,超过范围 a1==b1的结果是false

包装类型,调用equals方法进行比较的时候,底层会转化为基本类型,比较值。

思考:Integer e = new Integer(127)Integer f = 127 使用 == 比较,是否相等?使用 equals比较呢?

使用 == 比较返回的是false,因为是二个对象的比较,地址值不同。

使用 equals比较返回的是true,因为底层在比较的时候,会转化为基本数据类型,比较值。

1.1.21 JDK8和JDK11新特性有哪些

JDK8新特性

  1. Lambda表达式
  2. Stream流
  3. 接口默认方法
  4. 方法引用
  5. 函数式接口
  6. Optional
  7. 事件日期API

JDK11新特性

  1. 本地变量类型推断(Local Var):在Lambda表达式中,可以使用var关键字来标识变量,变量类型由编译器自行推断

1.1.22 Stream流中的常用方法有哪些

Stream流中的方法

  1. forEach:用于遍历Stream中的元素。
    1
    void forEach(Consumer<? super T> action);
  2. map:用于对流中的元素进行转换操作
    1
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  3. flatMap:用于将流中的每个值都转换为另一个流,然后把所有的流都连接成一个流,简单来说就是流的合并操作
    1
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
  4. filter:用于对Stream中的元素进行过滤,只保留满足条件的元素。
    1
    Stream<T> filter(Predicate<? super T> predicate);
  5. distinct:用于去除Stream中重复的元素。
    1
    Stream<T> distinct();
  6. limit:用于限制Stream中元素的数量,截取前n个元素。
    1
    Stream<T> limit(long maxSize);
  7. skip:用于跳过Stream中前n个元素。
    1
    Stream<T> skip(long n);
  8. sorted:用于对Stream中的元素进行排序。
    1
    2
    Stream<T> sorted();
    Stream<T> sorted(Comparator<? super T> comparator);
  9. max:用于获取Stream中最大的元素。
    1
    Optional<T> max(Comparator<? super T> comparator);
  10. min:用于获取Stream中最小的元素。
    1
    Optional<T> min(Comparator<? super T> comparator);
  11. reduce:用于对Stream中的元素进行统计,求和、求平均等操作。
    1
    T reduce(T identity, BinaryOperator<T> accumulator);
  12. collect:用于将Stream中的元素收集起来,转换成其他数据结构,如List、Set等。
    1
    <R, A> R collect(Collector<? super T, A, R> collector);
  13. anyMatch:判断流中是否存在任意元素满足条件
    1
    boolean anyMatch(Predicate<? super T> predicate);
  14. allMatch:判断流中是否所有元素满足条件
    1
    boolean allMatch(Predicate<? super T> predicate);

Collector采集器中的方法

  1. Collectors.toList():将流中的数据转化为List集合
    1
    public static <T> Collector<T, ?, List<T>> toList()
  2. Collectors.toSet():将流中的数据转化为Set集合
    1
    public static <T> Collector<T, ?, List<T>> toSet()
  3. Collectors.toMap():将流中的数据转化为Map集合
    1
    public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper)
  4. Collectors.groupingBy():对流中的数据进行分组
    1
    public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)

1.1.23 什么是深克隆和浅克隆,Java如何实现深克隆和浅克隆

深克隆和浅克隆是对象复制的两种方式。在Java中,实现深克隆和浅克隆的方法主要依赖于对象的实现方式。

浅克隆(Shallow Clone):只复制对象本身,对于对象内部的子对象则只复制其引用,而不是真正复制对象内部的内容。因此,如果子对象是可变的,修改原始对象中的子对象也会影响克隆对象中的子对象。

要实现浅克隆,需要让对象实现java.lang.Cloneable接口并重写Object类中的clone()方法。例如:

1
2
3
4
5
6
7
8
9
10
11
public class MyClass implements Cloneable {
private MySubClass subObject;

public MyClass clone() {
try {
return (MyClass) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // 不应该发生,因为我们是可克隆的
}
}
}

深克隆(Deep Clone):不仅复制对象本身,而且复制对象内部的子对象。因此,修改原始对象中的子对象不会影响克隆对象中的子对象。

要实现深克隆,需要在对象中定义一个方法来复制其内部的所有子对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClass implements Cloneable {
private MySubClass subObject;

public MyClass clone() {
try {
MyClass cloned = (MyClass) super.clone();
cloned.subObject = this.subObject.clone(); // 复制内部子对象
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // 不应该发生,因为我们是可克隆的
}
}
}

需要注意的是,如果子对象没有实现Cloneable接口或者没有正确地实现clone()方法,将会抛出CloneNotSupportedException异常。