LOADING

加载过慢请开启缓存 浏览器默认开启

Java基础整理

Java特性

面向对象,跨平台,健壮性,安全性,多线程,高性能,可移植性,分布式,动态性,简单性

Java如何实现跨平台

通过Java虚拟机(JVM)实现跨平台,
Java程序在编译后会生成字节码文件,字节码文件可以在任何支持Java虚拟机的平台上运行,因此Java是跨平台的。

Java和C++的区别

  1. Java是纯粹的面向对象语言,C++是混合型的面向对象语言,即也支持面向过程的编程。
  2. Java通过虚拟机从而实现跨平台,C++不支持跨平台。
  3. Java没有指针,C++有指针。
  4. Java有垃圾回收机制,C++没有垃圾回收机制,需要手动回收
  5. Java不支持多重继承,只能通过实现多个接口来达到相同目的。C++支持多重继承

面向对象的特性

封装,继承,多态和抽象

面向对象编程的六大原则

  1. 单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
  2. 里氏替换原则:所有引用基类对象的地方能够透明地使用其子类的对象。
  3. 迪米特法则:一个对象应该对其他对象保持最少的了解。也就是尽可能的降低类与类之间的耦合。
  4. 开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
  5. 依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。
  6. 接口隔离原则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象单一职责的情况下,尽量减少接口的内容。

Java的基本数据类型

byte,short,int,long,float,double,char,boolean
二进制位数:byte-8,short-16,int-32,long-64,float-32,double-64,char-16,boolean-1

为何不能用浮点型来代表货币

浮点型在计算机中是用二进制来表示的,而二进制无法精确的表示十进制小数,因此会出现精度丢失的情况,所以不能用浮点型来代表货币。建议使用BigDecimal或long来代替浮点型。

值传递和引用传递

值传递:传递的是变量的值,对形参的修改不会影响实参。传递的是该变量的一个副本。改变副本不影响原件。
引用传递:传递的是变量的地址,对形参的修改会影响实参。传递的是该变量的地址。改变地址的内容,就是改变原件。
Java中只有值传递,没有引用传递。Java中的引用传递是通过将引用的地址值进行传递,也就是说,传递的是引用的地址值,而不是引用本身。

Java的包装类型

// 实现包装类并调用
public class Test {
    public static void main(String[] args) {
        Integer i = new Integer(100);
        System.out.println(i);
    }
}

String为何是不可变的

String类是不可变的,因为String类中使用final关键字修饰了字符数组value,所以String类是不可变的。
原因:

  1. 线程安全
  2. 支持hash映射和缓存
  3. 字符串池的需要:String对象在Java中是重用的,因为String类是不可变的,所以可以共享使用,这样可以减少内存的开销,提高性能。
  4. 出于安全考虑:String类被广泛应用在Java的类库中,比如类加载器,Java反射机制等,如果String是可变的,那么会对安全性造成很大的威胁。

String的特性

  1. 不变性,字符串常量,可以保证数据的一致性
  2. 常量池优化,字符串常量池,可以减少内存的开销,提高性能
  3. final不能被继承,保证了String类的安全性

String、StringBuffer和StringBuilder的区别

  1. String是不可变的,StringBuffer和StringBuilder是可变的。
  2. 线程安全:String是线程安全的,不可变实现线程安全。StringBuffer是线程安全的,通过内部使用synchronized实现。StringBuilder是非线程安全的。

为何JDK9要将String的底层实现从char[]改为byte[]

  1. char[]占用内存空间更大,byte[]占用内存空间更小,可以节省内存空间。

什么是StringJoiner

StringJoiner是JDK8中新增的一个类,用来拼接字符串,可以指定分隔符、前缀和后缀。

// StringJoiner的使用
public class Test {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner(",", "[", "]");
        sj.add("Java");
        sj.add("Python");
        sj.add("C++");
        System.out.println(sj.toString());
    }
}

StringJoiner的构造方法:

  1. delimiter:分隔符
  2. delimiter、prefix、suffix:分隔符、前缀、后缀
// StringJoiner的构造方法
public StringJoiner(CharSequence delimiter)
public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

什么是字符串常量池

StringPool(字符串常量池)指的是在编译期被确定,并被保存在已编译的.class文件中的一些字符串,它包括了关键字(如:class,public,static,final等)、变量名、方法名等。StringPool存在于方法区中。
在创建字符串时,JVM会先去StringPool中查找是否存在相同值的字符串,如果存在,则直接返回StringPool中的字符串引用,否则,将此字符串添加到StringPool中,并返回此字符串的引用。

String s = new String(“abc”);创建了几个对象

创建了两个对象,一个是堆中的String对象,一个是常量池中的”abc”对象。

string最大长度

理论上没有长度限制,但是由于String类是不可变的,因此String对象的长度受到Integer.MAX_VALUE的限制,即2^31-1。
一个char占两个字节,这时需要一个4G的内存空间来运行JVM。
但是在字符串常量池中,字符串的长度受到JVM的限制,即65535。

深拷贝和浅拷贝

浅拷贝:对于基本数据类型,浅拷贝会直接复制一份,对于引用类型,浅拷贝会复制一份引用,不会复制对象本身。也就是说,浅拷贝不会创建一个新的对象,而是复制一个引用,指向原来的对象。
深拷贝:对于基本数据类型,深拷贝会直接复制一份,对于引用类型,深拷贝会复制一份对象本身。也就是说,深拷贝会创建一个新的对象,不再指向原来的对象。

hashcode相同,equals不一定相同,equals相同,hashcode一定相同。

为何重写equals方法时必须重写hashcode方法

因为在Java中,如果两个对象的equals方法相同,那么它们的hashcode方法也必须相同,否则会违反hashcode的约定,从而导致在使用hashmap等集合类时,会出现问题。

Java创建对象的几种方式

  1. 使用new关键字创建对象
  2. 使用Class类的newInstance方法创建对象,就是通过反射机制来创建对象
  3. 调用对象的clone方法来创建对象,就是通过克隆来创建对象
  4. 运用反序列化的方式来创建对象,调用java.io.ObjectInputStream对象的readObject方法来创建对象

类实例化的顺序

  1. 父类静态成员变量和静态代码块
  2. 普通成员变量和初始化代码块
  3. 构造函数

final、finally、finalize的区别

final用于修饰类、方法和变量,修饰类时,表示该类不能被继承,修饰方法时,表示该方法不能被重写,修饰变量时,表示该变量是一个常量,只能赋值一次。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾回收器执行的时候会调用被回收对象的finalize方法,可以覆盖此方法提供垃圾回收时的其他资源回收,例如关闭文件等。

重载和重写的区别

重载是指同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表。
重写是指子类可以重写父类的方法,方法名称、参数列表和返回值都相同。

接口与抽象类的区别

  1. 抽象类可以有构造方法,接口不能有构造方法,只能是public的抽象方法。
  2. 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型的。
  3. 接口中不能含有静态代码块和静态方法,抽象类中可以有静态代码块和静态方法。
  4. 一个类只能继承一个抽象类,但是可以实现多个接口。
  5. 抽象层次不同,抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。继承抽象类的子类是一个”是不是”的关系,实现接口的类是一个”有没有”的关系。如果一个类继承了一个抽象类,则子类必须实现抽象类中的所有方法,否则子类也必须是抽象类。如果一个类实现了一个接口,则类必须实现接口中的所有方法,否则类也必须是抽象类。
  6. 继承抽象类的子类可以是抽象类,也可以是非抽象类,实现接口的类必须是非抽象类。

常见的异常

  1. NullPointerException:空指针异常,当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
  2. ClassCastException:类型转换异常,当试图将对象强制转换为不是实例的子类时,抛出该异常。
  3. IndexOutOfBoundsException:数组越界异常,当应用程序试图访问数组的无效索引时,抛出该异常。
  4. ArrayStoreException:数组存储异常,当试图将错误类型的对象存储到一个对象数组时,抛出该异常。
  5. NumberFormatException:数字格式异常,当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
  6. ArithmeticException:算术异常,当出现异常的运算条件时,抛出该异常。
  7. NoSuchElementException:没有找到元素异常,当在集合中没有更多的元素可供操作,而又调用了集合中相应方法时,抛出该异常。
  8. NoSuchFileException:没有找到文件异常,当试图访问不存在的文件时,抛出该异常。
  9. ClassNotFoundException:找不到类异常,当应用程序试图根据字符串形式的类名构造或加载类对象,并且指定的类不存在时,抛出该异常。
  10. IllegalAccessException:非法访问异常,当应用程序尝试通过反射机制访问某个类的私有方法、变量时,抛出该异常。

Error和Exception的区别

error:JVM无法处理的错误,比如VirtualMachineError、OutOfMemoryError等,这些错误发生时,JVM一般会选择终止线程。
exception:JVM可以处理的异常,比如IOException、ClassNotFoundException等,这些异常发生时,JVM会选择继续运行程序。

Throwable类的常用方法

  1. getMessage():返回异常发生时的简要描述
  2. toString():返回异常发生时的详细信息
  3. getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化的异常信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同。
  4. printStackTrace():在控制台上打印Throwable对象封装的异常信息。

try-catch-finally的执行顺序

try: 用于捕获异常,其后面可以有零个或者多个catch块,如果没有catch块,则必须跟一个finally块。
catch: 用于处理try捕获到的异常。
finally: finally块用于存放一些无论是否发生异常都需要执行的代码。当在try或者catch块中遇到return语句时,finally语句块将在方法返回之前被执行。不要在finally块中使用return语句,否则程序会提前退出,返回值不是try或者catch中保存的返回值。

Finally块中的代码一定会执行吗

不一定,当在finally块中遇到System.exit(0)退出JVM或者遇到死循环语句时,finally块不会被执行。

try-with-resources语句

面对必须要关闭的资源时,优先使用try-with-resources语句,可以自动关闭资源,不需要手动关闭。

throw和throws的区别

throw用于方法内部,用来抛出一个异常对象,将异常对象传递到方法外部,由方法的调用者来处理。
throws用于方法声明后面,用来表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。

守护进程是什么

守护进程是一种特殊的进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程通常作为系统的后台服务进程存在,用来完成一些系统性的或周期性的工作。例如垃圾回收线程就是一个守护线程。

Java支持多继承吗?

Java不支持多继承,但是可以通过接口来实现多继承。

static属性为什么不会被序列化

因为static属性属于类的,不属于对象,序列化是针对对象的,所以不会被序列化。

什么是反射

反射是指程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。

反射的优缺点

优点:能够运行时动态获取类的实例,提高灵活性,可与动态编译结合
缺点:性能较差,需要解析字节码,将内存中的对象进行解析。可以通过setAccessible(true)来关闭安全检查,提高性能。多次创建一个类的实例时,有缓存会快很多,或者使用ReflectASM来提高性能。

获取反射中的Class对象

  1. 通过Object类的getClass()方法获取
  2. 类名.class,但这种方式只适合在编译期间就已经知道要加载的Class
  3. class.forName(),通过类的全限定名来获取Class对象
  4. 使用类加载器ClassLoader来获取Class对象,这种方式获取的对象不会进行初始化,也就是不会执行静态代码块和静态成员变量的初始化

Java反射API的主要类

  1. Class类:代表一个类,反射的核心类,可以获取类的属性,方法等信息
  2. Field类:代表类的成员变量,可以用来获取和设置类的成员变量的值
  3. Method类,代表类的方法,可以用来获取类的方法信息,以及调用方法
  4. Constructor类,代表类的构造方法,可以用来获取类的构造方法信息,以及创建对象

反射的使用步骤

  1. 获取Class对象
  2. 调用Class类中的方法,获取类的属性、方法、构造方法等信息
  3. 使用反射API获取的信息,创建对象,调用方法等
// 反射的使用步骤
public class Apple{
    private int price;
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    public static void main(String[] args)throws Exception{
        // 正常调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        // 反射调用
        Class cls = Class.forName("com.reflect.Apple");
        Method setPriceMethod = cls.getMethod("setPrice", int.class);
        Constructor appleConstructor = cls.getConstructor();
        // 使用Constructor类的newInstance方法获取反射类对象
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = cls.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

引入反射的原因

  1. 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能
  2. 反射让开发人员可以枚举出类的全部对象,包括构造函数,属性,方法等,可以帮助开发人员分析类,以及类之间的继承关系以写出正确的代码
  3. 测试时可以利用反射API访问类的私有成员,以保证测试代码覆盖率

反射的应用场景

  1. JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
public class ConnectionJDBC{
    public static final String DRIVER = "com.mysql.jdbc.Driver";
    public static final String URL = "jdbc:mysql://localhost:3306/test";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "root";
    public static void main(String[] args)throws Exception{
         // 连接对象并加载连接驱动
        Conmnnection conn = null;
        Class.forName(DRIVER);
        // 连接数据库
        Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        System.out.println(conn);
        // 关闭连接
        conn.close();
    }
}
  1. Eclispe、IDEA等开发工具利用反射动态解析对象的类型与结构,动态提示对象的属性和方法
  2. Web服务器中利用反射调用了Sevlet的service方法
  3. 动态代理、Spring IOC、Spring AOP、Hibernate、MyBatis等框架都用到了反射机制
public class DebugInvocationHandler implements InvocationHandler{
    // 被代理的对象
    private Object target;
    public DebugInvocationHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}
  1. 注解,注解是一种运行时动态生成的类,可以通过反射获取注解的信息

什么是泛型

允许在定义类和接口的时候使用类型参数,声明的类型参数在使用是用具体的类型来替换,这种机制称为泛型。

// 泛型的使用
public class Test<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
// 调用
Test<String> test = new Test<>();

泛型的使用方式

  1. 泛型类
  2. 泛型接口
  3. 泛型方法
// 泛型类
public class Test<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
// 实例化泛型类
Test<Integer> test = new Test<Interger>(123456);
// 泛型接口
public interface Generator<T> {
    public T next();
}
// 实现泛型接口
public class GeneratorImpl implements Generator<String> {
    @Override
    public String next() {
        return "Hello World";
    }
}
// 泛型方法
public static<E> void printArray(E[] inputArray){
        for (E element : inputArray){
            System.out.printf("%s ", element);
         }
         System.out.println();
    }

// 调用泛型方法
public static void main(String args[]){
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
 
        System.out.println("整型数组元素为:");
        printArray(intArray); // 传递一个整型数组
 
        System.out.println("\n双精度型数组元素为:");
        printArray(doubleArray); // 传递一个双精度型数组
 
        System.out.println("\n字符型数组元素为:");
        printArray(charArray); // 传递一个字符型数组
    }

项目中如何使用泛型

  1. 自定义接口通用返回结果
  2. 自定义通用的分页查询结果
  3. 定义Excel处理类,动态指定excel导出数据类型
  4. 构建集合工具类

泛型的优点

  1. 类型安全,编译器会检查类型,避免了类型转换的麻烦
  2. 消除强制类型转换,提高代码的可读性
  3. 潜在的性能收益

泛型中的限定通配符合非限定通配符

  1. 限定通配符:<? extends T>,表示类型的上界,表示参数化类型的可能是T或T的子类
  2. 非限定通配符:<?>,表示未知类型,表示参数化类型的可能是任何类型

序列化与反序列化

序列化:将对象转换为字节序列的过程称为对象的序列化。
反序列化:将字节序列恢复为对象的过程称为对象的反序列化。

throw和throws的区别

throw用于方法内部,用来抛出一个异常对象,将异常对象传递到方法外部,由方法的调用者来处理。

throws用在方法声明上,可以抛出多个异常,用来表示该方法可能抛出的异常列表

// throw和throws的区别
public static void main(String[] args){
    String s="abc";
    if(s.equals("abc")){
        throw new NumberFormatException();
    }else{
        System.out.println(Integer.parseInt(s));
    }
}
// throws
public static void function(String[] args) throws NumberFormatException{
    String s="abc";
    System.out.println(Integer.parseInt(s));
}
public static void main(String[] args){
    try{
        function();
    }catch(NumberFormatException e){
        e.printStackTrace();
    }
}

JVM处理异常的方式

如果发生异常,方法会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态,创建异常对象并交给JVM的过程称为抛出异常。

字节流转为字符流

通过InputStreamReader和OutputStreamWriter将字节流转为字符流。

字节流和字符流的区别

  1. 字节流以字节为单位读写数据,字符流以字符为单位读写数据
  2. 字节流适合所有类型文件的数据传输,字符流只适合处理文本数据,但是字符流处理文本数据更加方便

阻塞I/O和非阻塞I/O的区别

阻塞和非阻塞是指当某个时间或者任务在执行过程中,发出一个请求操作,但是由于该请求操作需要的条件不满足,是否会一直等待下去,如果是阻塞就是会一直等待下去,非阻塞就会返回一个标志信息告诉条件不满足,不会一直在那里等待。
BIO:同步阻塞,适合连接数目小且固定的架构
NIO:同步非阻塞,适合连接数目多且连接比较短的架构
AIO:异步非阻塞,适合连接数目多且连接比较长的架构

IO设计模式

  1. 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。分为

    类适配器模式和对象适配器模式。

  2. 装饰器模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

linux常用命令

  1. ls:查看当前目录下的文件
  2. cd:切换目录
  3. grep: 在文件中查找指定的字符串
  4. cat: 查看文件内容
  5. mkdir: 创建目录
  6. rm: 删除文件或目录
  7. mv: 移动文件或目录
  8. cp: 复制文件或目录
  9. find / -name “文件名”: 在根目录下查找文件
  10. touch: 创建文件
  11. less: 分页显示文件内容
  12. rwx: 读写执行权限
  13. tar: 打包文件

SPI机制

SPI全称为Service Provider Interface,是JDK内置的一种服务提供发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。个人认为就是专门提供给第三方实现的接口,第三方可以通过SPI机制来实现接口的扩展。

SPI将服务接口和具体的服务实现分离,将服务调用方和服务提供方解耦,能够提升程序的扩展性,可维护性,修改或者替换服务实现并不需要修改调用方的代码。

很多框架都使用了SPI机制,比如Dubbo、JDBC、JNDI、JCE、JAXB等。

API和SPI的区别

当实现方提供了接口和实现,可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是API,接口和实现都在实现方
当接口存在于调用方时,就是SPI,接口在调用方,实现在实现方

不希望被序列化的属性如何处理

使用transient关键字修饰属性,被transient修饰的属性不会被序列化。
但是transient只能修饰变量,不能修饰类和方法。
static变量由于不属于任何对象,所以不会被序列化

本文作者:GWB
当前时间:2023-11-09 11:11:10
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!