Java 是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此 Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。

0 目录

1 Java基础

1.1 数据类型

1.1.1 基本类型

基本类型字节包装类型缓冲池
int4字节Integer-128~127
byte1字节Byte 
short2字节Short-128~127
long8字节Long 
float4字节Float 
double8字节Double 
char2字节Character\u0000~\u007F
boolean~Booleantrue&false
  • JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。boolean 数组是通过读写 byte 数组来实现的。
// 装箱,自动将基本数据类型转换为包装器类型 调用了 Integer.valueOf(1)
Integer x = 1;
// 拆箱,自动将包装器类型转换为基本数据类型 调用了 X.intValue()
int y = x;
// 每次都会新建一个对象
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b); // false
// 使用缓存池中的对象,多次调用会取得同一个对象的引用,valueOf() 先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
Integer c = Integer.valueOf(1);
Integer d = Integer.valueOf(1);
System.out.println(c == d); // true

1.1.2 String

String 被声明为 final,因此它不可被继承。

  • 在 Java 8 中,String 内部使用 char 数组存储数据。
  • 在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。
  • 字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。在 Java 7,String Pool 被移到堆中,因为永久代的空间有限。

String 不可变的好处:

  1. 可以缓存 hash 值
  2. String Pool 的需要
  3. 安全性
  4. 线程安全
比较StringStringBufferStringBuilder
可变性不可变可变可变
线程安全安全安全不安全
// new String() 该方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)
// 1. 在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量
// 2. 在堆中创建一个字符串对象
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
// s.intern() 方法取得一个字符串引用。intern() 首先把 s 引用的字符串放到 String Pool 中,然后返回这个字符串引用。
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
// 字面量的形式创建字符串,会自动地将字符串放入 String Pool 中
String s5 = "abc";
String s6 = "abc";
System.out.println(s5 == s6); // true

1.2 Java容器

1.3 运算

1.3.1 传递

  • 基本类型
    • 值就直接保存在变量中。
    • 赋值运算符会直接改变变量的值,原来的值被覆盖掉。
  • 引用类型
    • 变量中保存的只是实际对象的地址。一般称这种变量为”引用”,引用指向实际对象,实际对象中保存着内容。
    • 赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变。

参数传递基本上就是赋值操作。Java 的参数是以值传递的形式传入方法中,而不是引用传递。

// eg1: 基本类型
void foo(int value) {
    value = 1;
}
foo(num); // num 没有被改变
// eg2: 没有提供改变自身方法的引用类型
void foo(String text) {
    text = "abc";
}
foo(str); // str 也没有被改变
// eg3: 提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("abc");
void foo(StringBuilder builder) {
    builder.append("1");
}
foo(sb); // sb 被改变了,变成了"abc1"。
// eg4: 提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("abc");
void foo(StringBuilder builder) {
    builder = new StringBuilder("def");
}
foo(sb); // sb 没有被改变,还是 "abc"。

1.3.2 类型转换

  • 1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。1.1f 字面量才是 float 类型。
  • 字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。但是使用 += 或者 ++ 运算符会执行隐式类型转换。

1.4 关键字

  • final
    • 对于基本类型,final 使数值不变;对于引用类型,final 使引用不变。
    • 声明方法不能被子类重写。private 方法隐式地被指定为 final。如果子类中定义的方法和基类中 private 签名相同,则是新方法。
    • 声明类不允许被继承。
  • static
    • 静态变量
      • 静态变量:又称为类变量,变量属于类,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
      • 实例变量:每创建一个实例就会产生一个实例变量。
    • 静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现。只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
    • 静态语句块在类初始化时运行一次。
    • 静态内部类
      • 非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。
      • 静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法。
    • 在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。

1.4.1 初始化顺序

存在继承的情况下,初始化顺序为:

  1. 父类(静态变量、静态语句块)
  2. 子类(静态变量、静态语句块)
  3. 父类(实例变量、普通语句块)
  4. 父类(构造函数)
  5. 子类(实例变量、普通语句块)
  6. 子类(构造函数)

1.5 Comparable与Comparator

比较|Comparable|Comparator :-:|:-:|:-: 位置|java.util|java.lang 重写方法|compareTo()|compare() 比较对象|任何类型|同一类型

1.6 JDK与JRE

  • JRE 是 JVM 程序,Java 应用程序需要在 JRE 上运行。
  • JDK 是用于开发 Java 程序的 JRE ,JRE + 工具的超集。例如,它提供了编译器“javac

2 Java面向对象

2.1 Object

  1. equals() 等价与相等
    • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
    • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
  2. hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。
    • 等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价;
    • 通过 hashCode() 和 equals() 必须能唯一确定一个对象;
    • 一旦重写了 equals() 函数,就必须重写 hashCode() 函数。
  3. toString() 默认返回 类名@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
  4. clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。使用 clone() 方法来拷贝一个对象即复杂又有风险,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
    • 浅拷贝拷贝对象和原始对象的引用类型引用同一个对象。默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
      • 对于基础类型,直接将属性值赋值给新的对象,其中一个对象修改该值,不会影响另外一个。
      • 对于引用类型,浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间,改变其中一个,会对另外一个也产生影响。
    • 深拷贝拷贝对象和原始对象的引用类型引用不同对象。
      • 对于基础类型,与浅拷贝一样。
      • 对于引用类型,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
      • 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法。
  5. wait() notify() notifyAll()

2.2 继承

  • 抽象类的特点:抽象类不能被实例化,只能被继承。
  • 接口是抽象类的延伸。
比较抽象类接口
关系is alike a
继承单个多个
成员访问权限无限制public
成员字段无限制属性:static final
方法:abstract
作用重用
把相同的东西提取出来
降低耦合
把程序模块进行固化的契约
  1. super
    • 可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
    • 子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
    • 如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
  2. 重写与重载
    • 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
      • 子类方法的访问权限必须大于等于父类方法;
      • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
      • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
      • 不能重写 private、final 修饰的方法
      • 使用 @Override 注解
    • 重载(Overload)存在于同一个类或接口中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
  3. 调用顺序
    • 在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
      1. this.func(this)
      2. super.func(this)
      3. this.func(super)
      4. super.func(super)

2.3 内部类和匿名类

内部类

  • 静态内部类
    • 只能访问外部类的静态方法、静态属性和 private 修饰的成员
    • 不依赖于外部类
  • 普通内部类
    • 可以直接访问外部类的属性、方法
    • 依赖于外部类
    • 不能声明 static 变量和方法

匿名类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明它们。匿名类的目的是在某个地方需要特殊的实现,因此在该处编写其实现,并获取它的实例,调用它的方法。

2.4 反射

Java 反射机制可以在程序中访问已经装载到 JVM 中的 Java 对象的描述,实现访问、检测和修改描述 Java 对象本身信息的功能。由 Class 和 java.lang.reflect 包提供支持。每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

反射机制相关的类

  • Package 类的存放路径:getPackage() 获取该类的存放路径
  • Class 类:getName() 获取该类的名称、getSuperclasses() 获取该类继承的类、getInterfaces() 获取该类实现的接口、getClasses() 获取所有内部类
  • Field 类的成员变量(类的属性):getFields() 获取权限为 public 的成员变量
  • Method 类的方法:getMethods() 获取权限为 public 的方法
  • Constructor 类的构造方法:getConstructors() 获取权限为 public 的构造方法

优缺点

  • 反射的优点:可扩展性、类浏览器和可视化开发环境、调试器和测试工具
  • 反射的缺点:性能开销、安全限制、内部暴露

2.4.1 动态代理

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。
    • 在程序运行前代理类的 .class 文件就已经存在了
    • 每个代理类只能为一个接口服务
  • 动态代理:在程序运行时运用反射机制动态创建而成。
    • 实现 java.lang.reflect.InvocationHandler 接口并重写 invoke() 方法
      • InvocationHandler 只能代理接口,invoke() 方法返回被代理接口的一个具体实现类
    • 使用 java.lang.reflect.Proxy 类的 newProxyInstance() 方法指定类装载器、一组接口及调用处理器生成动态代理类实例

2.5 枚举类型和泛型

2.5.1 枚举类型

enum 是定义枚举类型的关键字,它赋予程序在编译时进行检查的功能。使用:

  • 根据常量的值做不同操作
  • 根据枚举类型对象做不同操作

2.5.2 泛型

泛型,即“参数化类型”。将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(类型形参),然后在使用时传入具体的类型(类型实参)。泛型提供了编译时类型安全监测机制,该机制允许程序员在编译时监测非法的类型。泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。

  • 泛型类/接口(类名<T>):通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类。
  • 泛型方法:是在调用方法的时候指明泛型的具体类型。在 public 与返回值之间的 <T> 表明是一个泛型方法。

泛型的使用

  • 泛型的类型参数只能是类,不可以是基本数据类型
  • 泛型的类型个数可以有多个
  • 使用 extends 关键字限制泛型的类型
  • 使用通配符限制泛型的类型

类型擦除:Java 的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉

3 Java进阶

3.1 异常

3.1.1 异常分类

根据异常在编译时是否被检测

  • 受检异常 CheckedException。需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;
  • 非受检异常 UncheckedException。非受检异常不能在编译时检测到。非受检异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常是程序运行时错误,例如除 0 ,此时程序崩溃并且无法恢复。错误指的是致命性错误,常常无法处理。

Java 中的异常体系

  • Throwable 类是整个 Java 异常体系的超类,都有的异常类都是派生自这个类。包含 ErrorException 两个直接子类。对应的类是 java.lang.Throwable
    • Error 表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如 Java 虚拟机出现错误。在程序中不用捕获 Error 类型的异常;一般情况下,在程序中也不应该抛出 Error 类型的异常。对应的类是 java.lang.Error
    • Exception 是应用层面上最顶层的异常类,包含 RuntimeException(运行时异常)和其它异常。
      • RuntimeException 运行时异常:是一种非受检异常,即表示编译器不会检查程序是否对 RuntimeException 作了处理,在程序中不必捕获也不必抛出 RuntimeException。一般来说,RuntimeException 发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序。对应的类是 java.lang.RuntimeException。常见的 RuntimeException:
        • java.lang.ArithmeticException
        • java.lang.ArrayIndexOutOfBoundException
        • java.lang.ClassCastException
        • java.lang.IllegalArgumentException
        • java.lang.NullPointException
        • java.lang.NumberFormatException
      • 非运行时异常:是 RuntimeException 以外的异常,属于受检异常。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。常见的非运行时异常:
        • java.io.IOException
        • java.lang.SQLException
        • 自定义的异常

3.1.2 异常处理

  1. 捕捉异常 try...catch...finally...
    • catch 语句块和finally 语句块可以同时存在,也可以只有其一;
    • 如果发生异常,则会跳转到捕捉对应异常的 catch 语句块,发生异常语句之后的语句序列不会被执行;
    • 不管异常是否发生,finally 语句块总是会被执行,除非碰到强制退出程序的 System.exit(0) 语句;
    • 如果 try 语句块或 catch 语句块中有 return 语句,则会执行完 finally 语句块再执行返回。
  2. 抛出异常 throws
    • 声明但不去处理;
    • 在运行之后,该异常就会交给 JVM 处理,程序一旦遇到该异常,就会打印该异常的跟踪栈信息并结束程序。

3.2 JavaIO

3.3 Java虚拟机

3.4 Java并发

3.5 Java8新特性

3.5.1 Lambda表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。但是,只能用来简化仅包含一个 public 方法接口的创建。

语法格式

(parameters) -> expression
(parameters) -> { statements; }

分类及示例

// 1. 不需要参数,返回值为 5  
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x  y
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)
// 排序示例
private void sortUsingLambda(List<String> list){
  Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
}

3.5.2 其它

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具 − 新的编译工具,如:Nashorn 引擎 jjs、 类依赖分析器 jdeps。
  • Stream API − 新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到 Java 中。
  • Date Time API − 加强对日期与时间的处理。
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎 − Java 8 提供了一个新的 Nashorn javascript 引擎,它允许我们在JVM上运行特定的 javascript 应用。

3.6 与其它语言的对比

对比JavaC++
语种解释型语言编译型语言
平台相关性无关相关
指针
继承单继承
但可有多接口
多继承
操作符重载不支持支持
预处理功能不支持
import进行类似预处理
支持
goto
内存分配动态分配
交给JVM处理
C malloc() free()
C++ new delete
垃圾回收自动手动
⤧  Next post 【OO】面向对象 01 面向对象方法 ⤧  Previous post 【软件工程】《人件》书摘