Java面试基础知识回顾

Java 基础知识回顾

1. JDK 和 JRE 的区别

先从单词含义来理解这两个的区别,JDK( Java Development Kit) 意为 Java 开发工具包,而 JRE (Java Runtime Environment)意为 Java 运行时环境。

总的来说可以分为以下几个方面:

  1. 从文件结构上看,JDK 是包含了 JRE 的所有文件。
  2. 从功能上看,JDK 是一个完整的 SDK,集编译、运行等功能于一体,而 JRE 只提供运行时的环境。
  3. 从用途上来看,JDK 一般是用在开发环境上,便于编码人员经行开发,而 JRE 一般是用在生产环境上,只负责运行应用程序。(这里如果是部署 JSP 程序还是需要用 JDK,因为 JSP 会被 Web 服务器转换为 Java 类,而 Java类需要 JDK 编译后才能运行,而如果是部署 Spring boot 应用程序,则只需要 JRE)。

2. 泛型与类型擦除

2.1 泛型的类型

泛型的种类一共有三种,即 泛型类、泛型接口、泛型方法。
泛型类

public class People<T>{
	T phone;
}

泛型接口

public interface Eat<T>{
	void eatFood(T food);
}

泛型方法

public <T> void printAll(T[] arrays){
	for(T t : arrays){
		System.out.println(t);
	}
}

类型通配符
常用类型通配符有 :T,E,K,V,?。除 ?以外用法都基本相同。

这里 ?表示任意类型,使用方法有如下几种:

// 表示任意类型,但是不能直接操作该类型
public void printAll(List<?> list){
	for(Object o : list){
		System.out.println(o);
	}
}
// 限定 T,E,K,V、? 等通配的范围
public class People<T extends Phone, E extends Eat>{
	//这样 T 类型就只能是 Phone 类或其子类
	T phone;
	//这里 E 类型只能是 Eat 接口或其子接口或其实现类
	E eat;
	//限定通配范围
	public void printAll(List<? extends Phone> list){
		for(Phone phone : list){
			System.out.println(phone);
		}
	}
}

这里 ?只能代表抽象的广义通配,无法像 T 一样使用。

2.2 泛型的类型擦除

泛型的类型擦除是 Java 对泛型的一种实现方法,它是在编译 Java 代码的时候将泛型的类型擦除,只留下原本的类型。如 List<String>在类型擦除后只保留最基本的类型,即为Object,就变为了 List, 而像 List<T extends String> 在类型擦除后保留最基本的类型为 String,因为 T 是以 String 为基准的。

这里可以用一个实例来理解:

public void test(){
	List<Integer> list = new ArrayList();
	//这里编译时不会报错,因为 int 会自动包装为 Integer
	list.add(123);
	//这里编译时就会报错,因为类型匹配
	list.add("123");
	//利用反射在运行时添加就不会报错
	Method add = list.getClass().getMethod("add", Object.class);
	add.invoke(list, "123");
}

通过这个例子可以看到,Java 的泛型其实时一种伪泛型,只是在编译时将类型擦除,只能在编译期才能检查错误的发生,但是在运行期的泛型错误不能被查到。

更详细的解释可看: https://www.cnblogs.com/wuqinglong/p/9456193.html

3. equals() 和 hashCode()

3.1 equals() 和 ==

==: 它的作用是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型==比较的是内存地址)。
equals()==Object 类里其实是一样的操作,从源码可以看到:

 public boolean equals(Object obj) {
     return (this == obj);
 }

而很多类一般都会重写 equals() 方法,按照内容相等的逻辑来重写。
例如 Stringequals() 方法:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

它比较的就是内部的 char[] 的每个字符是否相等。

3.2 equals() 和 hashCode()

上面提到重写 equals() 方法可以实现用对象内容相等来判断是否相等。而重写 equals() 方法时又必须重写 hashCode() 这一方法,这是为了保证两个对象相等时 hashCode 必须相等。

这是因为 ObjecthashCode() 是根据对象的内存地址来算 hashCode 的,而在一些特定的场景,如将该类作为 HashMapkey 的数据类型时,由于首先需要计算 keyhashCode,才能计算出 hash 桶的位置,而这时如果重写类equals()方法,而不重写类的hashCode()方法,就会导致相同的 key 会定位到不同的桶上,可能会导致无法获取到数据。

4. 自动装箱与拆箱

4.1 概述

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型

4.2 8中基本类型的包装类和常量池

Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True Or False。如果超出对应范围仍然会去创建新的对象。
boolean 的自动装箱

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

Integer 的缓存源码

/**
*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Character 的常量池

private static class CharacterCache {         
    private CharacterCache(){}

    static final Character cache[] = new Character[127 + 1];          
    static {             
        for (int i = 0; i < cache.length; i++)                 
            cache[i] = new Character((char)i);         
    }   
}

这里只有 DoubleFloat 没有实现常量池。

5. 修饰符

5.1 static

static 关键字主要有以下四种使用场景:

  1. 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
  2. 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
  3. 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
  4. 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

5.2 final

final 关键字意为最终的、不可变得,可以用来修饰变量、方法、类。

  1. 修饰类:被 final 修饰的类无法被继承,其里面的成员方法都被隐式指定为 final 方法。
  2. 修饰方法:被 final 修斯的方法不能被重写。
  3. 修饰变量:final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。

说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。

6. StringBuilder 和 StringBuffer

简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[],所以 String 对象是不可变的。

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value;

StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。

StringBuilderStringBuffer 的构造方法都是调用父类构造方法也就是AbstractStringBuilder 实现的。

AbstractStringBuilder.java

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }}

线程安全性
String 里的 char[]final 类型,可以理解为常量,是线程安全的。AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappendinsertindexOf 等公共方法。 StringBuffer 是这些操作上加了同步锁,所以是线程安全的,而 StringBuilder 没有加同步锁,所以不是线程安全的。

对于三者使用的总结:

  • 操作少量的数据: 适用 String
  • 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值