JVM、JDK、JRE

JVM是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是使用相同的字节码,它们都会给出相同的结果。

JDK是功能齐全的Java SDK,它能够创建和编译程序。

JRE是Java运行时环境,它不能用于创建新程序。

8种基本数据类型

  • 6种数据类型
    • 4种整型:byte「1B」、short「2B」、int「4B」、long「8B」
    • 2种浮点型:float「4B」、double「8B」
  • 1种字符类型:char「2B」
  • 1种布尔型:boolean「1B」

基本类型和包装类型

  • 成员变量包装类型不赋值就是null,基本类型有默认值且不是null
  • 包装类型可用于泛型,而基本类型不可以
  • 基本数据类型的局部变量存放在虚拟机栈中,基本数据的成员变量「未被static修饰存放在虚拟机堆中。包装类型属于对象,对象实例都存在堆中

面向对象三大特征

封装、继承、多态

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

多态,顾名思义,表示一个对象拥有多种的状态,具体表现为父类的引用指向子类的实例。

  • 多态不能调用「只在子类存在但在父类不存在」的方法

深拷贝和浅拷贝

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

重写equals()和hashCode()

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。如果在HashMap中,这可能会导致出现重复的键值对。

字符串

  • 线程安全
    • String中的对象是不可变的,可以理解为常量,线程安全
    • StringBuffer加了同步锁,是线程安全的
    • StringBuilder不是线程安全的
  • 性能
    • 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象
    • StringBuilder比StringBuffer快一点

equals()

String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Objectequals 方法是比较的对象的内存地址

字符串常量池

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

try-catch-finally

不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。

面对必须要关闭的资源,总是应该优先使用try-with-resources而不是try-finally。例如InputStreamOutputStreamScanner等资源。

try-catch-finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}

try-with-resources

1
2
3
4
5
6
7
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}

当然也可以使用分号分割,在try-with-resources块中声明多个资源。

反射

什么是反射

反射赋予了我们在运行时分析类以及执行类中方法的能力。通过反射,可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。比如注解的实现也依赖于反射。

  • 优点:让代码更加灵活、为各种框架提供了便利
  • 缺点:增加了安全问题,另外反射的性能也稍微差点

反射实战

假设TargetObject是要使用反射操作的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.javaguide;

public class TargetObject {
private String value;

public TargetObject() {
value = "JavaGuide";
}

public void publicMethod(String s) {
System.out.println("I love " + s);
}

private void privateMethod() {
System.out.println("value is " + value);
}
}

使用反射操作这个类的方法以及参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package cn.javaguide;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
*/
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) targetClass.newInstance();
/**
* 获取 TargetObject 类中定义的所有方法
*/
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}

/**
* 获取指定方法并调用
*/
Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
String.class);

publicMethod.invoke(targetObject, "JavaGuide");

/**
* 获取指定参数并对参数进行修改
*/
Field field = targetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");

/**
* 调用 private 方法
*/
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}

重载和重写的区别

重载是指在同一个类中,方法名相同,但参数列表不同的多个方法。重载方法必须有不同的参数列表,可以有不同的返回类型和访问修饰符。重载方法可以被认为是一个新方法,只是与原来的方法名相同而已。

重写是指子类重新定义父类中已有的方法。重写方法必须有相同的名称、参数列表和返回类型。

抽象类和接口的区别

在Java中,抽象类和接口都是用于实现多态的机制。抽象类是对一种事物的抽象,而接口是对行为的抽象。

  • 一个类只能继承一个抽象类,但是可以实现多个接口
  • 接口只包含常量和抽象方法;抽象类可以包含抽象方法和非抽象方法

什么是泛型

Java泛型是JDK5中新引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如ArrayList<User> a = new ArrayList<User>()这行代码就指明必须传入User对象,传入其他类型的对象就会报错。

并且,原生 List 返回类型是 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换。

泛型类

1
2
3
4
5
6
7
8
9
10
11
public class Test<T> {
private T key;
public leetcode(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}

Test<Integer> test = new Test<Integer>(123); //实例化

泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Test<T> {
public T method();
}

//实现接口,不指定类型
public class TestImpl<T> implements Test<T>{
@Override
public T method() {
return null;
}
}

//实现接口,指定类型
public class TestImpl<T> implements Test<String>{
@Override
public String method() {
return "hello-world";
}
}

泛型方法

1
2
3
4
5
public void print(T[] array) {
for (T t : array) {
System.out.println(t);
}
}

注解

注解是一种元数据,可以将它理解为一种特殊的注释。它为我们在代码中添加信息提供了一种形式化的方法,它用于帮助我们更快捷的写代码。主要有四个作用:

  • 生成文档:即将元数据生成为Javadoc文档
  • 编译检查:编译器在编译阶段会对代码进行检查,例如@override会提示编译器查看是否重写了父类的方法
  • 编译动态处理:主要用作动态生成代码,例如一些帮助类、方法,通过注解实现自动生成
  • 运行动态处理:典型的例子是通过反射来注入实例

在实际的项目开发中见到的注解一般是来自 Java 自带的注解、元注解「修饰注解的注解,最基准的注解」、Spring 等框架的注解

参考文章:JavaGuide