常见的Java基础题(一)

108 阅读14分钟

1、Java语言有那些特点?

  • 面向对象(封装,继承,多态)

  • 平台无关性:(Write Once,Run any Where)具有良好的可移植性,保证这一点的是Java的虚拟机机制。

  • 支持多线程

  • 编译与解释并存

2、Java有哪些数据类型?

Java语言数据类型分为两种:基本数据类型和引用数据类型

基本数据类型:

  • 数值型:

    • 整数类型(byte、short、long)
    • 浮点类型(float、double)
  • 字符型(char)

  • 布尔型(boolean)

Java数据类型范围和默认值

基本类型位数字节默认值
int3240
short1620
long648oL
byte810
char162'u0000'
float3240f
double6480d
boolean1false

引用数据类型:

  • 类(class)
  • 接口(interface)
  • 数组

3、&和&&有什么区别?

&运算符有两种用法:短路与逻辑与

&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。

&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&。

例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。

4、面向对象的三大特征

  • 封装

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性方法。

  • 继承

继承是使用已存在的类的定义作为基础创建新的类,新的类定义可以增加新的属性或者新的方法,也可以继承父类的属性和方法。通过继承可以很方便地进行代码的复用。

继承的三个要点:

  1. 子类拥有父类对象的所有属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
  2. 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方法实现父类的方法。
  • 多态

所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。

在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并覆盖接⼝中同⼀⽅法)。

5、this关键字的作用

this是自身的一个对象,代表对象本身,可以理解为指向对象本身的一个指针

this的用法在Java中大体可以分为3中:

  1. 普通的直接引用,this想当于是指向当前对象本身
  2. 形参与成员变量名字重用,用this区分:
public Person(String name,int age){
    this.name = name;
    this.age =age;
}
  1. 引用本类的构造函数

6、抽象类和接口的区别

  1. 接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),⽽抽象类可以有⾮抽象的⽅法。
  2. 接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
  3. ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。
  4. 接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
  5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。

7、成员变量与局部变量的区别有哪些?

  1. 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

  2. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。

  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。

  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。

8、final关键字的作用是什么?

final表示不可变的意思,用于修饰类、属性和方法:

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可变,被final修饰的变量必须被显示地指定初始值,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。

例如:

 	fianl StringBuilder sb =new StringBuilder("abc");
	sb.append("d");
	System.out.println(sb);//abcd

9、final、finally、finalize的区别

  • final 用于修饰变量、方法和类:final 修饰的类不可被继承;修饰的方法不可被重写;修饰的变量不可变。

  • finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。

  • finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。

    一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。

10、==和equals的区别

== : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型 == 比较的是值,引⽤数据类型 == 比较的是内存地址)。

equals() : 它的作⽤也是判断两个对象是否相等。但是这个“相等”一般也分两种情况:

  • 默认情况:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时,等价于通过“ == ”比较这两个对象,还是相当于比较内存地址。
  • 自定义情况:类覆盖了 equals() ⽅法。我们平时覆盖的 equals()方法一般是比较两个对象的内容是否相同,自定义了一个相等的标准,也就是两个对象的值是否相等。

举个例⼦,Person,我们认为两个人的编号和姓名相同,就是一个人:

public class Person{
    private String no;
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return Objects.equals(no, person.no) &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(no, name);
    }
}

11、hashCode与equals

这个也是面试常问——“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode ⽅法?”

什么是 HashCode?

hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数,定义在 Object 类中, 是一个本地⽅法,这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。

public native int hashCode();

哈希码主要在哈希表这类集合映射的时候用到,哈希表存储的是键值对(key-value),它的特点是:能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码!

为什么要有 hashCode?

上面已经讲了,主要是在哈希表这种结构中用的到。

例如 HashMap 怎么把 key 映射到对应的 value 上呢?用的就是哈希取余法,也就是拿哈希码和存储元素的数组的长度取余,获取 key 对应的 value 所在的下标位置。

为什么重写 quals 时必须重写 hashCode ⽅法?

如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals ⽅法都返回 true。反之,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。

hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,则该 class 的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)

为什么两个对象有相同的 hashcode 值,它们也不⼀定是相等的?

因为可能会碰撞, hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。越糟糕的散列算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode )。

12、Java是值传递,还是引用传递?

Java 语言是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。

JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址。

而对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传递过去,因此引用类型也是值传递。

13、Java创建对象有哪几种方式?

  • new 创建新对象
  • 通过反射机制
  • 采用clone机制
  • 通过序列化机制

前两者都需要显式地调用构造方法。对于 clone 机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在 Java 中序列化可以通过实现 Externalizable 或者 Serializable 来实现。

14、String 和StringBuilder、StringBuffer的区别是?

  • String:String的值被创建后不饿能修改,任何对String的修改都会引发新的String对象的生成。
  • StringBuffer:和String类似,但是值可以被修改,使用synchronized来保证线程安全
  • StringBuilder:StringBuffer的非线程安全版本

15、Java中IO流分成哪几种?

流按照不同的特点,有很多种划分方式。

  • 按照流的流向分,可以分为输入流输出流
  • 按照操作单元划分,可以划分为字节流字符流
  • 按照流的角色划分为节点流处理流

Java Io 流共涉及 40 多个类,看上去杂乱,其实都存在一定的关联, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

IO-操作方式分类-图片来源参考[2]

16、有了字节流为什么还要有字符流?

其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。

所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

17、什么是反射?应用?原理?

什么是反射?

我们通常都是利用new方式来创建对象实例,这可以说就是一种“正射”,这种方式在编译时候就确定了类型信息。

而如果,我们想在时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射

通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

反射最核心的四个类:

Java反射相关类

反射的应用场景?

一般我们平时都是在在写业务代码,很少会接触到直接使用反射机制的场景。

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

像 Spring 里的很多 注解 ,它真正的功能实现就是利用反射。

就像为什么我们使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为我们可以基于反射操作类,然后获取到类/属性/方法/方法的参数上的注解,注解这里就有两个作用,一是标记,我们对注解标记的类/属性/方法进行对应的处理;二是注解本身有一些信息,可以参与到处理的逻辑中。

反射的原理?

我们都知道 Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。

18、jdk1.8的新特性

  • 接口默认方法:Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字修饰即可

  • Lambda 表达式和函数式接口:Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中),使用 Lambda 表达式使代码更加简洁,但是也不要滥用,否则会有可读性等问题,《Effective Java》作者 Josh Bloch 建议使用 Lambda 表达式最好不要超过 3 行。

  • Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合 Lambda 表达式可以方便的对集合进行处理。

    Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。

    简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

  • 日期时间 API:Java 8 引入了新的日期时间 API 改进了日期时间的管理。

  • Optional 类:用来解决空指针异常的问题。很久以前 Google Guava 项目引入了 Optional 作为解决空指针异常的一种方式,不赞成代码被 null 检查的代码污染,期望程序员写整洁的代码。受 Google Guava 的鼓励,Optional 现在是 Java 8 库的一部分。