Java自我修养-Java基础

244 阅读11分钟

声明:本篇内容是对博主「我是祖国的花朵」的《Java开发岗高频面试题全解析》的个人总结和补充

面向对象的三大特征:封装、继承和多态

  • 封装:将事物封装成一个类,减少耦合,隐藏细节。保留特定的接口与外界联系,当接口内部发生改变时,不会影响外部调用方。举例:① 在set方法中对age字段做逻辑校验;② 设计dubbo接口时参数封装为Param对象。

  • 继承:从一个已知的类中派生出一个新的类,新类可以拥有已知类的行为和属性,并且可以通过覆盖/重写来增强已知类的能力。明确:子类继承父类,子类拥有父类所有的属性和方法,但是父类的私有属性和方法,子类是不能访问的。不过可以通过父类public的get方法来获取父类中的private属性。

  • 多态:实现多态机制是依靠父类或接口的引用指向子类对象。好处:提高了代码的扩展性(用父类/接口形参接收子类实参)。

Java中类的初始化顺序

  • 初始化父类中的静态成员变量和静态代码块;
  • 初始化子类中的静态成员变量和静态代码块;
  • 初始化父类中的普通成员变量和代码块,再执行父类的构造方法;
  • 初始化子类中的普通成员变量和代码块,再执行子类的构造方法。

构造代码块和构造函数的区别: 构造代码块是给所有对象进行统一初始化, 构造函数给对应的对象初始化。

构造代码块的作用:它的作用就是将所有构造方法中公共的信息进行抽取。 举例:对字典类的enumDictsMap属性进行初始化。

重写(Override)与重载(Overload)

  • 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写
  • 重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。 image.png

JDK、JRE和JVM的区别

JDK、JRE和JVM的基本概念:

  • JDK(Java Development Kit)是一个开发工具包,是Java开发环境的核心组件,并且提供编译、调试和运行一个Java程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件;
  • JRE(Java Runtime Environment)是指Java运行时环境,是JVM的实现,提供了运行Java程序的平台。JRE包含了JVM,但是不包含Java编译器/调试器之类的开发工具;
  • JVM(Java Virtual Machine)是指Java虚拟机,当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。

区别与联系:

  • JDK是开发工具包,用来开发Java程序,而JRE是Java的运行时环境;
  • JDK和JRE中都包含了JVM;
  • JVM是Java编程的核心,独立于硬件和操作系统,具有平台无关性,而这也是Java程序可以一次编写,多处执行的原因。

引申:Java的跨平台性是如何实现的?① JVM屏蔽了操作系统和底层硬件的差异;② Java面向JVM编程,先编译生成字节码文件,然后交给JVM解释成机器码执行。

抽象类和接口的区别

  • 抽象类可以没有抽象方法,也可以抽象方法和非抽象方法共存;
  • 接口中的方法在JDK8之前只能是抽象的,JDK8开始提供了接口方法的default实现;(发散:default的作用? 举例:函数式接口Predicate中有抽象方法test() 且提供了默认方法negate()用来表示!test -> 泛型/函数式接口/Lambad )
  • 抽象类和类一样是单继承的;接口可以实现多个父接口;
  • 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量;
  • 在Java中,我们通过abstract来定义抽象类,通过interface关键字来定义接口。

瞎几把总结:抽象类和普通类完全一样,只是多了抽象方法。(抽象类有构造方法)

抽象类和接口应该如何选择?

根据抽象类和接口的不同之处(根据二者的区别进行分析),当我们仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量。

JDK8为什么会出现默认方法呐?

在JDK8中,为了给已经存在的接口增加新的方法并且不影响已有的实现,所以引入了接口中的默认方法实现。 官方应用:给java.util.Collection接口添加新方法,如stream()和parallelStream()方法根据传入参数获取串行流/并行流、forEach()方法增强for循环、removeIf()方法支持根据条件来删除集合元素等等。

Java的8中基本数据类型

  • byte:1字节
  • short:2字节
  • int:4个字节
  • long:8字节
  • float:4字节
  • double:8字节
  • char:2字节
  • boolean:Java规范中并没有规定boolean类型所占字节数

java int 类整数的最大值是2的31次方-1 = 2,147,483,647 (2^10 21亿)

Java中的元注解

Java中提供了4个元注解,元注解的作用是负责注解其它注解。

  • @Target:说明所修饰的对象范围,关键代码如下:
public @interface Target {  
    ElementType[] value();  
}  
public enum ElementType {  
  TYPE,FIELD,METHOD,PARAMETED,CONSTRUCTOR,LOCAL_VARIABLE,ANNOCATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE  
}  

例如,如下的注解使用@Target标注,表明MyAnn注解就只能作用在类/接口和方法上。

@Target({ElementType.TYPE, ElementType.METHOD})  
public @interface MyAnn {  
}
  • @Retention:保留策略定义了该注解被保留的时间长短。关键源码如下:
public @interface Retention { 
    RetentionPolicy value(); 
} 
public enum RetentionPolicy { 
    SOURCE, CLASS, RUNTIME 
}

其中,SOURCE:表示在源文件中有效(即源文件保留);CLASS:表示在class文件中有效(即class保留);RUNTIME:表示在运行时有效(即运行时保留)。例如,@Retention(RetentionPolicy.RUNTIME)标注表示该注解在运行时有效。

  • @Documented:该注解用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
  • @Inherited:该注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注解的作用: 代替繁杂的配置文件,简化开发。

当定义一个注解后,需要一个注解处理器来执行注解的内部逻辑。注解处理器定义了注解的处理逻辑,涉及到反射机制和线程机制等。

反射

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

面试:给出反射概念 -> 指出‘正射’与‘反射’(一般情况下,我们使用某个类时必定知道它是什么类,用来做什么的,可以直接new来对这个类实例化;而反射则是一开始我们并不知道要初始化的类对象是什么,自然也无法new来创建对象) -> Class类相关的类和使用 -> 反射的使用场景:IDEA提示/Web项目报错Method.invoke/spring xml配置中bean对象的定义 -> 引申:JVM如何创建实例/类加载器(双亲委派模型)

image.png

与反射相关的类

  • Class:表示类,用于获取类的相关信息;
  • Field:表示成员变量,用于获取实例变量和静态变量等;
  • Method:表示方法,用于获取类中的方法参数和方法类型等;
  • Constructor:表示构造器,用于获取构造器的相关参数和类型等;

获取Class类的三种基本方式

  1. 通过类名称.class来获取Class类对象:
Class c = int.class;
Class c = int[ ].class;
Class c = String.class;
  1. 通过对象.getClass( )方法来获取Class类对象:
Class c = obj.getClass( );
  1. 通过类名称加载类Class.forName( ),只要有类名称就可以得到Class:
Class c = Class.forName(“cn.ywq.Demo”);

给出一个反射的demo:

public class Demo {
    public static void main(String[] args) throws Exception {
        String className = "com.ywq.User";
        // 获取Class对象
        Class clazz = Class.forName(className);
        // 创建User对象
        User user = (User)clazz.newInstance();
        // 和普通对象一样,可以设置属性值
        user.setUsername("yangwenqiang");
        user.setPassword("19931020");
 
        System.out.println(user);
    }
}

反射的体现:市场上流行的各个框架,比如Spring等底层都依赖于Java中的反射机制。

深入学习反射:

  1. 大白话说Java反射:入门、使用、原理
  2. 学习java应该如何理解反射?

JDK8新特性

  • 接口中的default方法:forEach removeIf;
  • 反射机制;
  • 方法引用 函数式接口 Lambda;
  • HashMap:红黑树 扩容优化(2^n)

Java中Exception和Error的区别

image.png

Exception和Error的主要区别如下:

  • Exception是程序正常运行中预料到可能会出现的错误,并且应该被捕获并进行相应的处理,是一种异常现象
  • Error是正常情况下不可能发生的错误,Error会导致JVM处于一种不可恢复的状态,不需要捕获处理,比如说OutOfMemoryError

解析:

Exception又分为了运行时异常和编译时异常。

编译时异常(受检异常) 表示当前调用的方法体内部抛出了一个异常,所以编译器检测到这段代码在运行时可能会出异常,所以要求我们必须对异常进行相应的处理,可以捕获异常或者抛给上层调用方。

运行时异常(非受检异常) 表示在运行时出现的异常,常见的运行时异常包括:空指针异常,数组越界异常,数字转换异常以及算术异常等。

捕获异常应该遵循哪些原则?

  • 尽可能捕获比较详细的异常,而不是使用Exception一起捕获。
  • 当本模块不知道捕获之后该怎么处理异常时,可以将其抛给上层模块。上层模块拥有更多的业务逻辑,可以进行更好的处理。
  • 捕获异常后至少应该有日志记录,方便之后的排查。
  • 不要使用一个很大的try – catch包住整段代码,不利于问题的排查。

这些都是笔者血淋淋的教训,捕获到异常,却看不出是哪里抛出的,这才是绝望!!!

NoClassDefFoundError和ClassNotFoundException有什么区别?

  • 从名字中,我们可以看出前者是一个错误,后者是一个异常;
  • ClassNotFoundException:出现这种情况,一般都是类名字传入有误导致的。比如当我们使用例如Class.forName方法来动态的加载该类的时候,传入了一个类名,但是其并没有在类路径中被找到的时候,就会报ClassNotFoundException异常。
  • NoClassDefFoundError:如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。但是要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError。出现这种情况,一般是由于打包的时候漏掉了部分类或者Jar包被篡改已经损坏。

Todo:

  1. StringBuffer与StringBuilder的区别? -> Java中String,StringBuilder和StringBuffer的区别 -> Java中的字符串常量池
  2. Java中的泛型的理解 -> 🐶link
  3. Java序列化与反序列化的过程 -> Java 之 Serializable 序列化和反序列化的概念,作用的通俗易懂的解释
  4. equals和hashCode方法的关系?-> Java基础篇:什么是hashCode 以及 hashCode()与equals()的联系(hashCode主要是用于查找使用的,而equals()是用于比较两个对象是否相等的 举例HashMap)
  5. Java中equals方法和==的区别?-> Java基础篇:equals()方法与==的区别