【Java】Java 01 Java基础
Java
是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此 Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
0 目录
1 Java基础
1.1 数据类型
1.1.1 基本类型
基本类型 | 字节 | 包装类型 | 缓冲池 |
---|---|---|---|
int | 4字节 | Integer | -128~127 |
byte | 1字节 | Byte | |
short | 2字节 | Short | -128~127 |
long | 8字节 | Long | |
float | 4字节 | Float | |
double | 8字节 | Double | |
char | 2字节 | Character | \u0000~\u007F |
boolean | ~ | Boolean | true&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 不可变的好处:
- 可以缓存 hash 值
- String Pool 的需要
- 安全性
- 线程安全
比较 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全 | 安全 | 安全 | 不安全 |
// 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.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
equals()
等价与相等- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
hashCode()
返回哈希值,而 equals() 是用来判断两个对象是否等价。- 等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价;
- 通过 hashCode() 和 equals() 必须能唯一确定一个对象;
- 一旦重写了 equals() 函数,就必须重写 hashCode() 函数。
toString()
默认返回类名@4554617c
这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。clone()
是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。使用 clone() 方法来拷贝一个对象即复杂又有风险,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。- 浅拷贝拷贝对象和原始对象的引用类型引用同一个对象。默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
- 对于基础类型,直接将属性值赋值给新的对象,其中一个对象修改该值,不会影响另外一个。
- 对于引用类型,浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间,改变其中一个,会对另外一个也产生影响。
- 深拷贝拷贝对象和原始对象的引用类型引用不同对象。
- 对于基础类型,与浅拷贝一样。
- 对于引用类型,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
- 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法。
- 浅拷贝拷贝对象和原始对象的引用类型引用同一个对象。默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
- wait() notify() notifyAll()
2.2 继承
- 抽象类的特点:抽象类不能被实例化,只能被继承。
- 接口是抽象类的延伸。
比较 | 抽象类 | 接口 |
---|---|---|
关系 | is a | like a |
继承 | 单个 | 多个 |
成员访问权限 | 无限制 | public |
成员字段 | 无限制 | 属性:static final 方法:abstract |
作用 | 重用 把相同的东西提取出来 | 降低耦合 把程序模块进行固化的契约 |
super
- 可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
- 如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
- 重写与重载
- 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
- 不能重写 private、final 修饰的方法
- 使用 @Override 注解
- 重载(Overload)存在于同一个类或接口中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
- 重写(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
- 调用顺序
- 在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
this.func(this)
super.func(this)
this.func(super)
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() 方法指定类装载器、一组接口及调用处理器生成动态代理类实例
- 实现 java.lang.reflect.InvocationHandler 接口并重写 invoke() 方法
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 异常体系的超类,都有的异常类都是派生自这个类。包含Error
和Exception
两个直接子类。对应的类是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 异常处理
- 捕捉异常
try...catch...finally...
- catch 语句块和finally 语句块可以同时存在,也可以只有其一;
- 如果发生异常,则会跳转到捕捉对应异常的 catch 语句块,发生异常语句之后的语句序列不会被执行;
- 不管异常是否发生,finally 语句块总是会被执行,除非碰到强制退出程序的 System.exit(0) 语句;
- 如果 try 语句块或 catch 语句块中有 return 语句,则会执行完 finally 语句块再执行返回。
- 抛出异常
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 与其它语言的对比
对比 | Java | C++ |
---|---|---|
语种 | 解释型语言 | 编译型语言 |
平台相关性 | 无关 | 相关 |
指针 | 无 | 有 |
继承 | 单继承 但可有多接口 | 多继承 |
操作符重载 | 不支持 | 支持 |
预处理功能 | 不支持 import进行类似预处理 | 支持 |
goto | 无 | 有 |
内存分配 | 动态分配 交给JVM处理 | C malloc() free() C++ new delete |
垃圾回收 | 自动 | 手动 |