2020年Java基础面试必问一(内附代码详解)

**

Java【基础】面试题

Java【基础】
Java【集合】
Java【并发】
Java【网络】
Java【虚拟机】

**
字节和字符的区别?
字节是存储容量的基本单位;
字符是数字、字母、汉字以及其他语言的各种符号;
1 字节 = 8 个二进制单位,一个字符由一个字节或多个字节的二进制单位组成
& 和 && 的区别
Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边的表达式都为 true 的时候,整个运算结果才为 true,否则为 false。
&&:有短路功能,当第一个表达式的值为 false 的时候,则不再计算第二个表达式;
&:不管第一个表达式结果是否为 true,第二个都会执行。除此之外,& 还可以用作位运算符:当 & 两边的表达式不是 Boolean 类型的时候,& 表示按位操作

如何正确的退出多层嵌套循环?

  1. 使用标号和break;
  2. 通过在外层循环中添加标识符
    在.java文件内部可以有多少类(非内部类)?
    在一个java文件中只能有一个public公共类,但是可以有多个default修饰的类.

Java 中的参数传递时传值呢?还是传引用?
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
当传递方法参数类型为基本数据类型(数字以及布尔值)时,一个方法是不可能修改一个基本数据类型的参数。
当传递方法参数类型为引用数据类型时,一个方法将修改一个引用数据类型的参数所指向对象的值。即使 Java 函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。

什么是面向对象?
面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java 是一个支持并发、基于类和面向对象的计算机编程语言。面向对象软件开发具有以下优点:
代码开发模块化,更易维护和修改。
代码复用性强。
增强代码的可靠性和灵活性。
增加代码的可读性。

Java 对象创建的方式?
使用 new 关键字创建对象。
使用 Class 类的 newInstance 方法(反射机制)。
使用 Constructor 类的 newInstance 方法(反射机制)。
使用 clone 方法创建对象。
使用(反)序列化机制创建对象。
获取 Class 对象的 3 种方法
调用某个对象的 getClass()方法

Person p=new Person();
Class clazz=p.getClass();

调用某个类的 class 属性来获取该类对应的 Class 对象

Class clazz=Person.class;

使用 Class 类中的 forName()静态方法(最安全/性能最好)
Class clazz=Class.forName(“类的全路径”); (最常用)
举例:

//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
//获取 Person 类的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
}
//获取 Person 类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}
//获取 Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}

抽象类和接口有什么区别?
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。
Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 #main(String[] args) 方法的话是可以被调用的。

请说说面向对象的特征?
1)封装
封装,给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在 Java 当中,有 4 种修饰符: default、public、private 和 protected 。
每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。
通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。
下面列出了使用封装的一些好处:
通过隐藏对象的属性来保护对象内部的状态。
提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。
禁止对象之间的不良交互提高模块化。
2)继承
继承,给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。
继承是从已有类得到继承信息创建新类的过程
关于继承的几点补充:
(1)子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象;
(2)子类可以拥有自己属性和方法;
(3)子类可以用自己的方式实现父类的方法。(重写)
3)多态
多态,是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作,可以应用到其他类型的值上面。
分为编译时多态(方法重载)和运行时多态(方法重写)。要实现多态需要做两件事:一是子类继承父类并重写父类中的方法,二是用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。
Java的多态机制:
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

4)抽象
抽象,是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。
Java 支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。
面向对象和面向过程的区别?
面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。比如,单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低。

Java 中的几种基本数据类型是什么?各自占用多少字节?
Java 支持的数据类型包括基本数据类型和引用类型。
基本数据类型如下:
整数值型:byte、short、int、long
字符型:char
浮点类型:float、double
布尔型:boolean
整数型:默认 int 型,小数默认是 double 型。Float 和 Long 类型的必须加后缀。比如:float f = 100f 。
引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。
引用类型包括类、接口、数组等。
特别注意,String 是引用类型不是基本类型。
java中的8种基本数据类型及其字节数
数据类型 关键字 字节数
数值型 整数型 byte 1
short 2
int 4
long 8
浮点型 float 4
double 8
布尔型 boolean 1(位)
字符型 char 2

重载和重写的区别?
1)重写 override
方法名、参数、返回值相同。
子类方法不能缩小父类方法的访问权限。
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
存在于父类和子类之间。
方法被定义为 final 不能被重写。
2)重载 overload
参数类型、个数、顺序至少有一个不相同。
不能重载只有返回值不同的方法名。
存在于父类和子类、同类中。
(1)重载:编译时多态、同一个类中同名的方法具有不同的参数列表、不能根据返回类型进行区分【因为:函数调用时不能指定类型信息,编译器不知道你要调哪个函数】;
(2)重写(又名覆盖):运行时多态、子类与父类之间、子类重写父类的方法具有相同的返回类型、更好的访问权限。

Java 中,什么是构造方法?什么是构造方法重载?什么是拷贝构造方法?
1)构造方法
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java 编译器会为这个类创建一个默认的构造方法。
2)构造方法重载
Java 中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
3)拷贝构造方法
Java 不支持像 C++ 中那样的拷贝构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java 不会创建默认的拷贝构造方法。
构造器是否可以被重写?
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 Override(重写),但是可以 Overload(重载),所以你可以看到一个类中有多个构造函数的情况。
构造方法有哪些特性?
(1)名字与类名相同;
(2)没有返回值,但不能用 void 声明构造函数;
(3)成类的对象时自动执行,无需调用。

在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,
而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。
解决办法是:在父类里加上一个不做事且没有参数的构造方法。

JDK、JRE、JVM 分别是什么关系?
JDK
JDK 即为 Java 开发工具包,包含编写 Java 程序所必须的编译、运行等开发工具以及 JRE。开发工具如:
用于编译 Java 程序的 javac 命令。
用于启动 JVM 运行 Java 程序的 Java 命令。
用于生成文档的 Javadoc 命令。
用于打包的 jar 命令等等。
简单说,就是 JDK 包含 JRE 包含 JVM。
JRE
JRE 即为 Java 运行环境,提供了运行 Java 应用程序所必须的软件环境,包含有 Java 虚拟机(JVM)和丰富的系统类库。系统类库即为 Java 提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
简单说,就是 JRE 包含 JVM。
JVM
JVM 即为 Java 虚拟机,提供了字节码文件(.class)的运行环境支持。

为什么 Java 被称作是“平台无关的编程语言”?
Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。
Java 源文件( .java )被编译成能被 Java 虚拟机执行的字节码文件( .class )。
Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
JDK 各版本的新特性?
对于大多数面试官,肯定不会问你 JDK 各版本的新特性,更多的会问 JDK8 引入了什么重要的特性?一般上,关键的回答是Lambda 表达式和集合之流式操作,然后说说你在项目中怎么使用的。
Lambda表达式
lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

先来体验一下lambda最直观的优点:简洁代码

  //匿名内部类
  Comparator<Integer> cpt = new Comparator<Integer>() {
      @Override
      public int compare(Integer o1, Integer o2) {
          return Integer.compare(o1,o2);
      }
  };

  TreeSet<Integer> set = new TreeSet<>(cpt);

  System.out.println("=========================");

  //使用lambda表达式
  Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);
  TreeSet<Integer> set2 = new TreeSet<>(cpt2);

只需要一行代码,极大减少代码量!!
这样一个场景,在商城浏览商品信息时,经常会有条件的进行筛选浏览,例如要选颜色为红色的、价格小于8000千的….

// 筛选颜色为红色


public  List<Product> filterProductByColor(List<Product> list){
    List<Product> prods = new ArrayList<>();
    for (Product product : list){
        if ("红色".equals(product.getColor())){
            prods.add(product);
        }
    }
    return prods;
 }
// 筛选价格小于8千的
public  List<Product> filterProductByPrice(List<Product> list){
    List<Product> prods = new ArrayList<>();
    for (Product product : list){
        if (product.getPrice() < 8000){
            prods.add(product);
        }
    }
    return prods;
 }

我们发现实际上这些过滤方法的核心就只有if语句中的条件判断,其他均为模版代码,每次变更一下需求,都需要新增一个方法,然后复制黏贴,假设这个过滤方法有几百行,那么这样的做法难免笨拙了一点。如何进行优化呢?

优化一:使用设计模式

//定义一个MyPredicate接口
public interface MyPredicate <T> {
    boolean test(T t);
}

如果想要筛选颜色为红色的商品,定义一个颜色过滤类

public class ColorPredicate implements MyPredicate <Product> {

     private static final String RED = "红色";

     @Override
     public boolean test(Product product) {
         return RED.equals(product.getColor());
     }

定义过滤方法,将过滤接口当做参数传入,这样这个过滤方法就不用修改,在实际调用的时候将具体的实现类传入即可。

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

例如,如果想要筛选价格小于8000的商品,那么新建一个价格过滤类既可

public class PricePredicate implements MyPredicate<Product> {
    @Override
    public boolean test(Product product) {
        return product.getPrice() < 8000;
    }
}

这样实现的话可能有人会说,每次变更需求都需要新建一个实现类,感觉还是有点繁琐呀,那么再来优化一下

优化二:使用匿名内部类

定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

调用过滤方法的时候:

// 按价格过滤
public void test2(){
    filterProductByPredicate(proList, new MyPredicate<Product>() {
        @Override
        public boolean test(Product product) {
            return product.getPrice() < 8000;
        }
    });
}
 // 按颜色过滤
 public void test3(){
     filterProductByPredicate(proList, new MyPredicate<Product>() {
         @Override
         public boolean test(Product product) {
             return "红色".equals(product.getColor());
         }
     });
 }

`

使用匿名内部类,就不需要每次都新建一个实现类,直接在方法内部实现。看到匿名内部类,不禁想起了Lambda表达式。

优化三:使用lambda表达式

定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){
        List<Product> prods = new ArrayList<>();
        for (Product prod : list){
            if (mp.test(prod)){
                prods.add(prod);
            }
        }
        return prods;
    }

使用lambda表达式进行过滤

@Test
public void test4(){
      List<Product> products = filterProductByPredicate(proList, (p) -> p.getPrice() < 8000);
      for (Product pro : products){
          System.out.println(pro);
      }
  }

优化四:使用Stream API
甚至不用定义过滤方法,直接在集合上进行操作

// 使用jdk1.8中的Stream API进行集合的操作
@Test
public void test(){
    // 根据价格过滤
    proList.stream()
           .fliter((p) -> p.getPrice() <8000)
           .limit(2)
           .forEach(System.out::println);

    // 根据颜色过滤
    proList.stream()
           .fliter((p) -> "红色".equals(p.getColor()))
           .forEach(System.out::println);

    // 遍历输出商品名称
    proList.stream()
           .map(Product::getName)
           .forEach(System.out::println);
}

Lmabda表达式的语法总结: () -> ( );
前置 语法
无参数无返回值

() -> System.out.println(“Hello WOrld”)

有一个参数无返回值

(x) -> System.out.println(x)

有且只有一个参数无返回值

x -> System.out.println(x)

有多个参数,有返回值,有多条lambda体语句

(x,y) -> {System.out.println(“xxx”);return xxxx;}

有多个参数,有返回值,只有一条lambda体语句

(x,y) -> xxxx

Java 和 C++ 的区别?
都是面向对象的语言,都支持封装、继承和多态。
Java 不提供指针来直接访问内存,程序内存更加安全。
Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
【重要】Java 有自动内存管理机制,不需要程序员手动释放无用内存。
什么是字节码?采用字节码的最大好处是什么?
什么是字节码?
这个问题,面试官可以衍生提问,Java 是编译执行的语言,还是解释执行的语言。
Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。
Java 源代码
=> 编译器 => JVM 可执行的 Java 字节码(即虚拟指令)
=> JVM => JVM 中解释器 => 机器可执行的二进制机器码 => 程序运行
采用字节码的好处?
Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
解释型语言:解释型语言,是在运行的时候将程序翻译成机器语言。解释型语言的程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。——百度百科
例如:Python、PHP 。
什么是值传递和引用传递?
值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。
一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。
是否可以在 static 环境中访问非 static 变量?
static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。
如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
Java 中是否可以重写一个 private 或者 static 方法?
Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
Java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用, 如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖
char 型变量中能不能存贮一个中文汉字?为什么?
在 C 语言中,char 类型占 1 个字节,而汉字占 2 个字节,所以不能存储。
在 Java 语言中,char 类型占 2 个字节,而且 Java 默认采用 Unicode 编码,一个 Unicode 码是 16 位,所以一个 Unicode 码占两个字节,Java 中无论汉字还是英文字母,都是用 Unicode 编码来表示的。所以,在 Java 中,char 类型变量可以存储一个中文汉字。
String、StringBuffer、StringBuilder 的区别?
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它们可以储存和操作字符串。
String ,是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer/StringBuilder 类,表示的字符串对象可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结?
操作少量的数据 = String 。
这个也是实际编码较为经常使用的方式。
单线程操作字符串缓冲区下操作大量数据 = StringBuilder 。
甚至有时,我们为了避免每个线程重复创建 StringBuilder 对象,会通过 ThreadLocal + StringBuilder 的方式,进行对 StringBuilder 的重用。具体可以参考 《StringBuilder 在高性能场景下的正确用法》 文章。
多线程操作字符串缓冲区下操作大量数据 = StringBuffer
实际场景下,我们基本不太会出现,多线程操作同一个 StringBuffer 对象。
String s = new String(“xyz”) 会创建几个对象?
首先,在 String 池内找,找到 “xyz” 字符串,不创建 “xyz” 对应的 String 对象,否则创建一个对象。
然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。
所以,总共是 1 个或者 2 个对象。
具体的,可以看看 《关于String s = new String(“xyz”); 创建几个对象的问题》 文章的测试代码。
String 为什么是不可变的?
简单的来说,String 类中使用 final 关键字字符数组保存字符串。代码如下:

// String.java
private final char[] value;

所以 String 对象是不可变的。
而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[] value ,但是没有用 final 关键字修饰。代码如下:

// AbstractStringBuilder.java
char[] value;

所以这两种对象都是可变的。
String 为什么要设计为不可变类?
1)字符串常量池的需要:字符串常量池是 Java 堆内存中一个特殊的存储区域, 当创建一个 String 对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;
(2)允许 String 对象缓存 HashCode:Java 中 String 对象的哈希码被频繁地使用, 比如在 HashMap 等容器中。字符串不变性保证了 hash 码的唯一性,
因此可以放心地进行缓存。这也是一种性能优化手段,意味着不必每次都去计算新的哈希码;
(3)String 被许多的 Java 类(库)用来当做参数,例如:网络连接地址 URL、文件路径 path、还有反射机制所需要的 String 参数等, 假若 String 不是固定不变的,将会引起各种安全隐患。
String 字符串修改实现的原理?
当用 String 类型来对字符串进行修改时,其实现方法是首先创建一个 StringBuffer,其次调用 StringBuffer 的 append() 方法,
最后调用 StringBuffer 的 toString() 方法把结果返回。

String str = “i” 与 String str = new String(“i”) 一样吗?
不一样,因为内存的分配方式不一样。String str = “i” 的方式,Java 虚拟机会将其分配到常量池中;而 String str = new String(“i”) 则会被分到堆内存中。

String 类的常用方法都有那些?
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

StringTokenizer 是什么?
StringTokenizer ,是一个用来分割字符串的工具类。
示例代码如下:
StringTokenizer st = new StringTokenizer(”Hello World”);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
输出如下:
Hello
World
什么是自动拆装箱?
自动装箱和拆箱,就是基本类型和引用类型之间的转换。
为什么要转换?
如果你在 Java5 下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合( Collection )中放入原始类型值,因为集合只接收对象。
通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用 Integer、Double、Boolean 等这些类,我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。
为了让代码简练,Java5 引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。
但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的 Bug 。
int 和 Integer 有什么区别?
int 是基本数据类型。
Integer 是其包装类,注意是一个类。
当然,要注意下 Integer 的缓存策略。
int 型变量的默认值是 0,Integer 变量的默认值是 null,这一点说明 Integer 可以区分出未赋值和值为 0 的区分;
Integer 变量必须实例化后才可以使用,而 int 不需要。
包装类的缓存:
Boolean:全部缓存
Byte:全部缓存
Character:<= 127 缓存
Short:-128 — 127 缓存
Long:-128 — 127 缓存
Integer:-128 — 127 缓存
Float:没有缓存
Doulbe:没有缓存

equals 与 == 的区别?
值类型(int,char,long,boolean 等)的话
都是用 == 判断相等性。
对象引用的话
== 判断引用所指的对象是否是同一个。
equals 方法,是 Object 的成员函数,有些类会覆盖(override) 这个方法,用于判断对象的等价性。
例如 String 类,两个引用所指向的 String 都是 “abc” ,但可能出现他们实际对应的对象并不是同一个(和 JVM 实现方式有关),因此用 == 判断他们可能不相等,但用 equals 方法判断一定是相等的。
如何在父类中为子类自动完成所有的 hashCode 和 equals 实现?这么做有何优劣?
父类的 equals ,一般情况下是无法满足子类的 equals 的需求。
比如所有的对象都继承 Object ,默认使用的是 Object 的 equals 方法,在比较两个对象的时候,是看他们是否指向同一个地址。但是我们的需求是对象的某个属性相同,就相等了,而默认的 equals 方法满足不了当前的需求,所以我们要重写 equals 方法。
如果重写了 equals 方法,就必须重写 hashCode 方法,否则就会降低 Map 等集合的索引速度。
说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法?
这个问题,和上个 「如何在父类中为子类自动完成所有的 hashCode 和 equals 实现?这么做有何优劣?」 一样的答案。
这样的 a.hashCode() 有什么用,与 a.equals(b) 有什么关系?
这个问题,和上述问题,就是换个姿势,差不了太多。
equals 方法,用于比较对象的内容是否相等。
当覆盖了 equals 方法时,比较对象是否相等将通过覆盖后的 equals 方法进行比较(判断对象的内容是否相等)。
hashCode 方法,大多在集合中用到。
将对象放入到集合中时,首先判断要放入对象的 hashCode 值与集合中的任意一个元素的 hashCode 值是否相等,如果不相等直接将该对象放入集合中。
如果 hashCode 值相等,然后再通过 equals 方法判断要放入对象与集合中的任意一个对象是否相等,如果 equals 判断不相等,直接将该元素放入到集合中,否则不放入。
有没有可能 2 个不相等的对象有相同的 hashCode?
可能会发生,这个被称为哈希碰撞。当然,相等的对象,即我们重写了 equals 方法,一定也要重写 hashCode 方法,否则将出现我们在 HashMap 中,相等的对象作为 key ,将找不到对应的 value 。
所以说,equals 和 hashCode 的关系会是:
equals 不相等,hashCode 可能相等。
equals 相等,请重写 hashCode 方法,保证 hashCode 相等。
一般来说,hashCode 方法的重写
final、finally、finalize 的区别?
1)final
final ,是修饰符关键字。
如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract 的,又被声明为 final 的。
将变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写。
另外,在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的private 方法都隐式地指定为 final 。
2)finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
在以下 4 种特殊情况下,finally块不会被执行:
在 finally 语句块中发生了异常。
在前面的代码中用了 System.exit() 退出程序。
程序所在的线程死亡。
关闭 CPU 。
3)finalize
finalize ,是方法名。
Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
它是在 Object 类中定义的,因此所有的类都继承了它。
子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。
#finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。
final 修饰 StringBuffer 后还可以 append 吗?
可以。final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。
finally 是不是一定会被执行到?
不一定。下面列举两种执行不到的情况:
(1)当程序进入 try 块之前就出现异常时,会直接结束,不会执行 finally 块中的代码;
(2)当程序在 try 块中强制退出时也不会去执行 finally 块中的代码,比如在 try 块中执行 exit 方法。

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会。程序在执行到 return 时会首先将返回值存储在一个指定的位置,其次去执行 finally 块,最后再返回。因此,对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉;
而对引用类型是有影响的,返回的是在 finally 对 前面 return 语句返回对象的修改值。

String 类能被继承吗,为什么?
不能,因为 String 是 final 修饰。
继承和组合的区别在哪?
继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。在 Java 中,此类关系通过关键字 extends 明确标识,在设计时一般没有争议性。
组合:组合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即 has-a 的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。
比如,计算机与 CPU 、公司与员工的关系等。
表现在代码层面,和关联关系是一致的,只能从语义级别来区分。
因为组合能带来比继承更好的灵活性,所以有句话叫做“组合优于继承”。
请详细讲述一下 RandomAccess 接口有什么作用?
RandomAccess 用来当标记的,是一种标记接口,接口的非典型用法。意思是,随机访问任意下标元素都比较快。
用处,当要实现某些算法时,会判断当前类是否实现了 RandomAccess 接口,会根据结果选择不同的算法。
讲讲类的实例化顺序?
初始化顺序如下:
父类静态变量
父类静态代码块
子类静态变量、
子类静态代码块
父类非静态变量(父类实例成员变量)
父类构造函数
子类非静态变量(子类实例成员变量)
子类构造函数
什么是内部类?
简单的说,就是在一个类、接口或者方法的内部创建另一个类。这样理解的话,创建内部类的方法就很明确了。

Java 类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根
据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。

静态内部类(定义在类内部的静态类,就是静态内部类)

public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
  1. 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。
  2. 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
  3. 其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示: Out.Inner inner =
    new Out.Inner();inner.print();
  4. Java集合类HashMap内部就有一个静态内部类Entry。 Entry是HashMap存放元素的抽象,
    HashMap 内部维护 Entry 数组用了存放元素,但是 Entry 对使用者是透明的。像这种和外部
    类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

成员内部类(定义在类内部的非静态类,就是成员内部类)
成员内部类不能定义静态方法和变量(final 修饰的
除外)。这是因为成员内部类是非静态的, 类初始化的时候先初始化静态成员,如果允许成员内
部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的

public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
}
}

局部内部类(定义在方法中的类)
定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。

public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(c);
}
}
}
}

匿名内部类(要继承一个父类或者实现一个接口、直接使用
new 来生成一个对象的引用)
匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一
个接口。同时它也是没有 class 关键字,这是因为匿名内部类是直接使用 new 来生成一个对象的引

public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird){
System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
});
}
}

内部类的作用是什么?
内部类提供了更好的封装,除了该外围类,其他类都不能访问。
Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
可以继承其他类或实现其他接口,在 Java 集合的流式操作中,我们常常这么干。
内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
什么是 Java IO ?
Java IO 相关的类,在 java.io 包下,具体操作分成面向字节(Byte)和面向字符(Character)两种方式。如下图所示:
2020年Java基础面试必问一(内附代码详解)_第1张图片

JAVA 泛型
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本
质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 比如我们要写一个排序方法,
能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型

泛型方法()
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数
类型,编译器适当地处理每一个方法调用。

// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
}
1.	<? extends T>表示该通配符所代表的类型是 T 类型的子类。
2. <? super T>表示该通配符所代表的类型是 T 类型的父类。

泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一
样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,
也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,
这些类被称为参数化的类或参数化的类型

public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}

类型通配符?
类 型 通 配 符 一 般 是 使 用 ? 代 替 具 体 的 类 型 参 数 。 例 如 List 在 逻 辑 上 是
List,List 等所有 List<具体类型实参>的父类。

什么是 Java 序列化?(创建可复用的 Java 对象)
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
反序列化的过程,则是和序列化相反的过程。
另外,我们不能将序列化局限在 Java 对象转换成二进制数组,例如说,我们将一个 Java 对象,转换成 JSON 字符串,或者 XML 字符串,这也可以理解为是序列化。
如何实现 Java 序列化?
如下的方式,就是 Java 内置的序列化方案,实际场景下,我们可以自定义序列化的方案,例如说 Google Protobuf 。
将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。
序列化
然后,使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
接着,使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。
反序列化
要恢复的话则用输入流。
Serializable 实现序列化:
在 Java 中, 只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
Java 序列话中,如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰。
当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。
当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
transient 只能修饰变量,不能修饰类和方法。

什么情况下需要序列化?
(1)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
(2)当你想用套接字在网络上传送对象的时候;
(3)当你想通过 RMI 传输对象的时候。
如何实现对象克隆?
一般来说,有两种方式:
1、实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。
2、实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。
这个问题,也可以变种来问,什么是浅克隆和深克隆。
实际场景下,我们使用的克隆比较少,更多是对象之间的属性克隆。例如说,将 DO 的属性复制到 DTO 中,又或者将 DTO 的属性复制到 VO 中。此时,我们一般使用 BeanUtils 工具类。

JAVA 复制
将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式
是浅拷贝,第三种是深拷贝。所以大家知道了哈,这三种概念实际上都是为了拷贝对象

直接赋值复制
直接赋值。在 Java 中, A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是
说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候, a2 里面的成员变量也会跟
着变化。
浅复制(复制引用但不复制引用的对象)
创建一个新对象,然后将当前对象的非静态字段复制到该新对象, 如果字段是值类型的,
那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。
因此,原始对象及其副本引用同一个对象。

深复制(复制对象和其应用对象)
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
【异常】error 和 exception 有什么区别?CheckedException 和 RuntimeException 有什么区别?
如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下
会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用
这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
Java 的异常体系,基于共同的祖先 java.lang.Throwable 类。如下图所示:
2020年Java基础面试必问一(内附代码详解)_第2张图片

  1. 图中的 ArrithmeticException 异常,多了一个 r ,正确拼写是 ArithmeticException
  2. 图中 ClassNotFoundException 异常,父类是 ReflectiveOperationException => Exception ,不属于 RunTimeException
    Error(错误),表示系统级的错误和程序不必处理的异常,是 Java 运行环境中的内部错误或者硬件问题。
    例如:内存资源不足等。
    对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由 Java 虚拟机抛出的。
    Exception(异常),表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。Exception 又分为运行时异常,受检查异常。
    RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止逻辑,因此,编译器不检查这些异常。
    CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理),所以称之为受检查异常。

异常分类
Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception
Error

  1. Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果
    出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
    Exception(RuntimeException、 CheckedException)
  2. Exception 又 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一 个 是
    CheckedException。
    RuntimeException 如 : NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常
    CheckedException,如 I/O 错误导致的 IOException、 SQLException。 RuntimeException 是
    那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一
    定是程序员的错误.
    13/04/2018 Page 102 of 283
    检查异常 CheckedException: 一般是外部错误,这种异常都发生在编译阶段, Java 编译器会强
    制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一
    般包括几个方面:
  3. 试图在文件尾部读取数据
  4. 试图打开一个错误格式的 URL
  5. 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存

异常的处理方式
遇到问题不进行具体处理,而是继续抛给调用者 (throw,throws)
抛出异常有三种形式,一是 throw,一个 throws,还有一种系统自动抛异常。

**public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
}
int div(int a,int b) throws Exception{
return a/b;}**

try catch 捕获异常针对性处理方式
Throw 和 throws 的区别:
位置不同

  1. throws 用在函数上,后面跟的是异常类,可以跟多个; 而 throw 用在函数内,后面跟的
    是异常对象。
    功能不同:
  2. throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方
    式; throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并
    将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语
    句,因为执行不到。
  3. throws 表示出现异常的一种可能性,并不一定会发生这些异常; throw 则是抛出了异常,
    执行 throw 则一定抛出了某种异常对象。
    13/04/2018 Page 103 of 283
  4. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异
    常,真正的处理异常由函数的上层调用处理。
    异常的使用的注意地方?
    不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流而使用异常)。
    对可以恢复的情况使用受检异常,对编程错误使用运行时异常。
    避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)。
    优先使用标准的异常。
    每个方法抛出的异常都要有文档。
    保持异常的原子性
    不要在 catch 中忽略掉捕获到的异常。
    Throwable 类常用方法?
    #getMessage() 方法:返回异常发生时的详细信息。
    #getCause() 方法:获得导致当前 Throwable 异常的 Throwable 异常。
    #getStackTrace() 方法:获得 Throwable 对象封装的异常信息。
    #printStackTrace() 方法:在控制台上打印。
    请列出 5 个运行时异常?
    NullPointerException
    IndexOutOfBoundsException
    ClassCastException
    ArrayStoreException
    BufferOverflowException
    throw 与 throws 的区别 ?
    throw ,用于在程序中显式地抛出一个异常。
    throws ,用于指出在该方法中没有处理的异常。每个方法必须显式指明哪些异常没有处理,以便该方法的调用者可以预防可能发生的异常。最后,多个异常用逗号分隔。
    异常处理中 finally 语句块的重要性?
    不管程序是否发生了异常, finally 语句块都会被执行,甚至当没有catch 声明但抛出了一个异常时, finally 语句块也会被执行。
    finally 语句块通常用于释放资源, 如 I/O 缓冲区, 数据库连接等等。
    异常被处理后异常对象会发生什么?
    异常对象会在下次 GC 执行时被回收。
    说说反射的用途及实现?
    动态语言
    动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结
    构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,
    而 C、 C++则不属于动态语言。 从反射角度说 JAVA 属于半动态语言。

反射机制概念 (运行状态中知道类所有的属性和方法)

2020年Java基础面试必问一(内附代码详解)_第3张图片

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

反射的应用场合
编译时类型和运行时类型
在 Java 程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由
声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。 如:

Person p=new Student();

其中编译时类型为 Person,运行时类型为 Student。
的编译时类型无法获取具体方法
程序在运行时还可能接收到外部传入的对象, 该对象的编译时类型为 Object,但是程序有需要调用
该对象的运行时类型的方法。为了解决这些问题, 程序需要在运行时发现对象和类的真实信息。
然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象
和类的真实信息,此时就必须使用到反射了。

Java 反射 API
反射 API 用来生成 JVM 中的类、接口或则对象的信息。

  1. Class 类:反射的核心类,可以获取类的属性,方法等信息。
  2. Field 类: Java.lang.reflec 包中的类, 表示类的成员变量,可以用来获取和设置类之中的属性
    值。
  3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或
    者执行方法。
  4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。
    反射使用步骤(获取 Class 对象、调用对象方法)
  5. 获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方
    法。
  6. 调用 Class 类中的方法,既就是反射的使用阶段。
  7. 使用反射 API 来操作这些信息。
    获取 Class 对象的 3 种方法:
    调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();

调用某个类的 class 属性来获取该类对应的 Class 对象

Class clazz=Person.class;

使用 Class 类中的 forName()静态方法(最安全/性能最好)

Class clazz=Class.forName("类的全路径"); (最常用)

当我们获得了想要操作的类的 Class 对象后,可以通过 Class 类中的方法获取并查看该类中的方法
和属性。

//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person

");

//获取 Person 类的所有方法信息

Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
}

//获取 Person 类的所有成员属性信息

Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}

//获取 Person 类的所有构造方法信息

Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}

Java 反射机制主要提供了以下功能:
在运行时构造一个类的对象。
判断一个类所具有的成员变量和方法。
调用一个对象的方法。
生成动态代理。
反射的应用很多,很多框架都有用到:
Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
Spring MVC 的请求调用对应方法,也是通过反射。
JDBC 的 Class#forName(String className) 方法,也是使用反射。
反射中,Class.forName 和 ClassLoader 区别?
这两者,都可用来对类进行加载。差别在于:
Class#forName(…) 方法,除了将类的 .class 文件加载到JVM 中之外,还会对类进行解释,执行类中的 static 块。
ClassLoader 只干一件事情,就是将 .class 文件加载到 JVM 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。
Class#forName(name, initialize, loader) 方法,带参函数也可控制是否加载 static 块,并且只有调用了newInstance 方法采用调用构造函数,创建类的对象。
UnsupportedOperationException 是什么?
UnsupportedOperationException ,是用于表明操作不支持的异常。
在 JDK 类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection 将会在所有 add 和 remove 操作中抛出这个异常。
什么是注解?
2020年Java基础面试必问一(内附代码详解)_第4张图片

Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径
和方法。 Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation
对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

2020年Java基础面试必问一(内附代码详解)_第5张图片
2020年Java基础面试必问一(内附代码详解)_第6张图片

4 种标准元注解
元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被
用来提供对其它 annotation 类型作说明。

2020年Java基础面试必问一(内附代码详解)_第7张图片

@Target 修饰的对象范围
@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、 types(类、
接口、枚举、 Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数
和本地变量(如循环变量、 catch 参数) 。在 Annotation 类型的声明中使用了 target 可更加明晰
其修饰的目标
@Retention 定义 被保留的时间长短
Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描
述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:
 SOURCE:在源文件中有效(即源文件保留)
 CLASS:在 class 文件中有效(即 class 保留)
 RUNTIME:在运行时有效(即运行时保留)
@Documented 描述-javadoc
@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因
此可以被例如 javadoc 此类的工具文档化。
@Inherited 阐述了某个被标注的类型是被继承的
@Inherited 元注解是一个标记注解, @Inherited 阐述了某个被标注的类型是被继承的。如果一
个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该
class 的子类。

提取注解方法

2020年Java基础面试必问一(内附代码详解)_第8张图片

什么时候用断言(assert)?
断言,在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。
一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。
断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为 false ,那么系统会报告一个AssertionError 错误。断言的使用如下面的代码所示:
assert(a > 0); // throws an AssertionError if a <= 0
断言可以有两种形式:
assert Expression1; 。
assert Expression1 : Expression2; 。
Expression1 应该总是产生一个布尔值。
Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。
要在运行时启用断言,可以在启动 JVM 时使用 -enableassertions 或者 -ea 标记。要在运行时选择禁用断言,可以在启动 JVM 时使用 -da 或者 -disableassertions 标记。要在系统类中启用或禁用断言,可使用 -esa 或 -dsa 标记。还可以在包的基础上启用或者禁用断言。
当然,实际场景下,我们会在 Spring 的源码中看到,它自己封装了 Assert 类,实现更方便的断言功能,并且,在生产环境下也启用。
另外,在单元测试中,也会使用自己封装的断言类,判断执行结果的正确与错误。

你可能感兴趣的:(面试系列)