Skip to content

基本概念

Java 的优势:生态 一些常见概念

  • JDK(Java Development Kit):Java SDK,一套基本的开发套件,包括了 JRE
  • JRE(Java Runtime Environment):Java 运行时环境,包括了 JVM
  • JVM:java 虚拟机,运行 Java 字节码,是一次编译,随处运行的关键
  • JIT(Just in Time Compilation):编译器,
  • 字节码:.java文件通过 javac 编辑后生成的 .class 内容,之后

Java 和 C++

  • 都是面向对象的语言,都支持封装、继承和多态
  • Java 不提供指针直接访问内存,更安全
  • Java 单继承多实现,C++多继承
  • Java 自动内存管理,C++ 手动
  • Java 只支持方法重载,C++支持方法和操作符重载
  • Java 只有值传递,引用变量传递的是对象的引用,形参不会改变实参的指向,只能改变指向对象的值
    • C++有引用传递,形参变化可以改变实参的执行

基本语法

移位运算符

Java 中有三种移位运算符:只能用于整形

  • << :位左移,高位丢弃,低位补零。x << 1,相当于 x 乘以 2(不溢出的情况下)。
  • >> :位右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1,相当于 x 除以 2。
  • >>> :无符号右移,忽略符号位,空位都以 0 补齐。
  • 如果移位的位数超过数值所占有的位数会先取余

基本数据类型

  • 6 种数字类型:
    • 4 种整数型:byteshortintlong
    • 2 种浮点型:floatdouble
  • 1 种字符类型:char,占用两个字节
  • 1 种布尔型:boolean
  • Java 的基本类型的存储空间与平台无关,所以移植性比较好
  • 包装类型就是首字母大写的完整拼写,包装类型是类
  • 基本数据类型一般放在栈中(不是对象的成员变量),对象实例基本都是在堆中

包装类型

  • 整型:Byte、Short、Integer、Long。缓存范围通常是-128到127。
    • 整型通过BigInteger存储任意大小的整型数(但是性能较慢),底层是 int 数组
  • 字符型:Character。其缓存范围是0到127的字符。
  • 布尔型:Boolean。缓存了TRUE和FALSE两个对象。
  • 浮点数:Float 和 Double,没有缓冲,因为范围太大,缓存效果不明显
    • 浮点数通过BigDecimal实现精度不丢是问题(性能较慢),称为定点数
    • 底层通过 BigInteger+用于存储小数点后的位数的 int 值实现
  • 直接赋值如Interger i = 10;会使用缓存对象,即多个引用指向同一个对象,但是通过直接 new,如Interger in = new Interge(10)会创建新对象
    • 包装类使用 equals 进行比较
  • 自动装箱和拆箱
    • 装箱:将基本类型用引用类型包装起来,如Integer i = 10;
      • 底层调用valueOf()方法,等价于 Integer i = Integer.valueOf(10)
    • 拆箱:将包装类型转换为基本数据类型,如int n = i;
      • 底层调用xxxValue()方法,如 floatValue(),等价于 int n = i.intValue();
    • 频繁装拆箱会影响一定的性能

变量

  • 成员变量属于类,可以用可见性修饰符和 static 修饰,存放在堆中,与对象或者类共享生命周期
  • 成员变量会有默认零值,局部变量不会自动赋值
    • final 修饰的成员变量必须显示赋值
  • static 修饰的是静态变量,被类的所有实例共享,一般会用 final 修饰,避免被随意篡改
    • 可以直接通过类名.变量名调用,实例变量只能引用变量.变量名调用

方法

  • 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。可以访问静态变量,不能访问实例变量
  • 非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。两种变量都能访问
  • 在类的非静态成员不存在的时候静态方法就已经存在了,此时静态方法调用在内存中还不存在的非静态成员,属于非法操作。

重载和重写

  • 重载:编译期,同一个类或者父子类,方法名相同,参数列表不同(顺序、个数等)
    • 编译器自动匹配,如果编译器找不到,会产生编译错误
    • 一个类中多个同名方法根据不同的传参执行不同的逻辑处理
    • 返回值和访问修饰符可以不同
  • 重写:运行期,子类对父类允许访问的方法的实现过程进行重新编写,两同两小一大
    • 两同方法名、参数列表完全相同
    • 两大:返回值类型,抛出的异常类都比父类小
    • 一大:访问权限比父类更大

可变长参数

  • public static void method1(String... args),可以接受 0 或者 n 个参数,只能作为函数的最后一个参数,前面可以有其他参数
    • 这里 args 其实就是一个数组
  • 方法重载会优先匹配固定参数的方法,其匹配度更高

面向对象基础

  • 面向过程:将问题拆分成方法。
  • 面向对象:先抽象出对象,将问题拆分到不同对象。
    • 封装:封装了内部实现细节,只能通过 getter/setter 来访问或修改
    • 继承:代码复用,子类继承父类的属性和方法,无需重新定义(不能使用父类的私有属性和方法),是多态的基础
    • 多态:父类引用指向子类实例,可以通过统一接口处理不同类型对象,减少代码中重复的部分
      • 多态下,能用的方法必须父类有,但是实际使用是从子类往上找,最近实现的

对象实体与对象引用

  • new 创建的是对象实体,存在堆中(也可能栈)
  • 对象引用指向对象实体,存放在栈中,一个对象实体可以有多个对象引用,一个对象引用指向一个对象实体或 0 个对象实体(null)

对象的相等和引用的相等

  • 对象的相等一般比较的是内存中存放的内容是否相等。
  • 引用相等一般比较的是他们指向的内存地址是否相等。

抽象类和接口的差别

  • 抽象类用 abstract 定义,接口用 interface
  • 抽象类单继承,接口多实现
  • 抽象类有构造方法,接口无
  • 抽象类可以有非抽象方法,接口只能都是抽象方法
  • 抽象类成员变量和方法有不同访问控制级别,成员变量和方法默认都是 public
  • 两者都不能直接实例化,子类或实现类必须实现他们定义的所有抽象方法
  • 这两个主要是一种规范

Object 常见方法

  • hashCode():哈希表使用,确定对象在哈希表的索引位置
    • 如果 hashcode 不同,对象一定不同
    • 如果 hashcode 相同,再通过 equals 判定是否相等,只有 equals 也是 true,才是完全相同
    • 如果重写了 equals,没有重写 hashcode,可能导致相同的对象 hashcode 不同,存放在哈希表的不同桶,导致异常
      • 因为默认 hashcode 返回的是地址的映射,如果重写了 equals,那不同地址内容相同的对象可能相等,但是 hashcode 不相等
  • equals():默认比较两个对象地址
    • 如果没有重写,等价于使用 ====对于引用类型比较的是对象的地址,对于基本数据类型,比较的是值
    • 一般自己实现
  • toString():print 中默认调用这个方法,默认返回的是「类的全限定名 + @ + 对象哈希码的十六进制表示」,一般重写
  • wait()
  • notify()
  • clone():默认浅拷贝,可以重写实现深拷贝

构造方法

  • 一个类没有声明构造方法,会有默认不带参数的构造方法
  • 声明了构造方法,默认无参构造就没了,需要手动添加无参构造
    • Spring 框架这些会用到无参构造,所以最好手动声明,避免异常
  • 构造方法不能被重写,但可以重载(最简单就是有参和无参)

拷贝

  • 浅拷贝:如果原对象内部的属性是引用类型的话,会直接复制内部对象的引用地址,共用同一个内部对象。
  • 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
  • 引用拷贝:两个引用指向同一个对象,上面两者都会新建一个对象

String、StringBuffer、StringBuilder 的区别

  • 三者通过byte 数组存储字符串
  • String 是不可变的,另外两个底层都是字符数据,可变
  • StringBuilder没有加同步锁,线程不安全,另外两个安全
  • StringBuffer有锁,性能较低
    • String使用+底层也是调用StringBuilder的 append 之后调用toString()得到对象,每次新建StringBuilder对象,性能更低
    • JDK9 之后,不再新建StringBuilder对象,性能有所提升
  • String 的 equals 是重写过的,比较的是字符串的值是否相等
  • String s1 = new String("abc")会创建 1 到 2 个字符串对象,abc字面量(如果已经存在不会创建),new String 会基于字面量创建一个新的 String 对象
  • String 类保存字符串的数组被 final 修饰且私有,并且没有暴露修改数组的方法
    • final 修饰保证了指向不可变,没有暴露修改方法保证了内容不可变
    • String 类也是 final 修饰,不能被继承,防止被子类破坏其不可变性
    • 不可变保证了安全性,密码等配置大多使用 String 保存,如果可变,可能导致崩溃
      • 其他地方获取了这个引用变量改变了其值,是引用拷贝后修改引用指向的对象,对原引用指向不会带来变更
  • 如果编译器可以确定的字符串,编译器会优化,如 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";
    • 变量的话运行时才能确定,不会优化

异常

层级:

  • Throwable 类:顶层
    • Exception 类:继承自Throwable类,表示程序可以处理的异常,可以通过 catch 捕获
      • Checked Exception:受检异常,必须处理,如果没有被 catch 或者 throws,无法编译
        • 运行时异常之外的
      • Unchecked Exception:不受检异常,可以不处理
        • RuntimeException 及其子类
    • Error类:继承自Throwable类,表示无法处理的错误,不建议 catch 捕获
      • OOM、NoClassFoundError 等,JVM 一般选择线程终止
  • 常用方法:
    • String getMessage(): 返回异常发生时的简要描述
    • String toString(): 返回异常发生时的详细信息
    • String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
    • void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息

异常捕获

try-catch-finally

  • try块:接可能抛出异常的代码
  • catch块:异常捕获的处理方案,可以有 0 个会多个,如果没有,必须有 finally 块
  • finally 块:无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
    • 如果 finally 中也有 return,会覆盖 try/catch 中的 return,try 和 catch 的 return 是互斥的
    • 如果程序异常终止,如被 kill -9 或者宕机,finally 中代码也是无法执行的
    • 一般用于资源的关闭或者监控的上报

try-with-resources

  • 一般用于输入输出流等需要手动调用 close() 方法的场合,可以省略 finally 中的关闭
    • 如果有多个资源,在 try() 中用分号分割多个资源,很简单,try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt"))))

泛型

  • 作用
    • 1、动态在使用时确定类型,避免重复定义的冗余,比如去重,可以一个方法实现所有类型的去重(基本类型通过自动装箱)
    • 2、编译期类型安全,避免运行时类型转换异常,如 ArrayList 没有泛型只能保存 Object 类去兼容所有类型,那么在编译期无法确定这个列表对象是不是都给到了指定的类型,如果滥用,可能导致运行时才会察觉异常
      • 泛型的话,比如List<String> list = new ArrayList<>();,那么list.add(123);在编译器就能察觉到错误
  • 使用场合
    • 泛型类:public class Generic<T>,实例化时指定类别,内部属性方法可以使用T这个类型
      • 使用:new Generic<Integer>(1);
    • 泛型接口:public interface Generator<T>,实现泛型接口时可以指定或者不指定类型
    • 泛型方法:public static <E> void printArray(E[] inputArray),使用时不需要指定类别,自动推断
      • 静态泛型方法没办法使用类上声明的泛型,因为类对象先于实例对象产生

注解

一种特殊的注释,用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

序列化和反序列化

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
  • 一般用于网络传输或将对象存储到文件中的场景
  • 不想被序列化的变量,使用 transient 关键字修饰。
    • 只能修饰变量,不能修饰方法
    • transient关键字修饰的变量,反序列化后会得到默认值
    • static变量不属于任何对象,一定不会被序列化
    • 但是有一个 serialVersionUID是 static 变量,会被序列化,这个用于判定类的定义是否发生变化,如果变化了,反序列化会抛出异常(类似于类的 hash)
  • JDK 自带的序列化性能差,且不能跨平台,一般使用 protobuf/protoStuff 这些

正在精进