JavaSE
JavaSE技术范围
JavaSE范围包括Java基础和Java高级部分
基础部分:数据类型、变量、运算符、循环结构、数组、面向对象(以封装、继承和多态展开来讲)
高级部分:多线程基础、常用类于API、枚举类、注解、反射、泛型、IO基础、JVM虚拟机基础
基础部分
数据类型
数据类型分为:基本类型和引用类型
基本数据类型很好理解,即8种基本数据类型:byte、short、int、long、float、double、char、float
默认值是依据作为成员变量时,如果不显示初始化时,在类对象实例化过程中,被填充的默认值
类型 | 空间 | 默认值 |
---|---|---|
byte | 1字节 | 0 |
short | 2字节 | 0 |
int | 4字节 | 0 |
long | 8字节 | 0L |
float | 4字节 | 0.0f |
double | 8字节 | 0.0d |
char | 2字节 | ‘\u0000’ |
boolean | 1字节 | false |
引用类型也很好理解,即:类、接口和数组
在平常的代码书写时,外面用到的直接量即字面量,整数字面量整型(int)和小数字面量双精度浮点型(double)
关于char
/**
* 1.char 占用2个byte空间即 16bit
* 2.char 可以与int进行转换,毕竟int类型的范围包含char
* 3.char 的特殊字符转义字符
* 换行符 \n\r (windows下)
* 制表符 \t
* 4.可以用Unicode来进行char表示 Unicode是16进制表示 '\u0020'
*/
变量
变量分类
变量分为静态变量、成员变量和局部变量
静态变量:一般在类加载过程时,静态变量(static)被放入方法区内
成员变量:一般指实例变量,类的实例变量,对象拥有的
局部变量:指的是方法内声明的变量(基本类型、引用类型),一旦方法结束后,所占空间将被GC回收
局部变量的说明
什么是局部变量:局部变量就是在方法体或代码块中声明的变量,方法执行完或代码块执行完,该变量就会被弹出栈。实际上,局部变量是保存在栈中(虚拟机栈),局部变量表。特别注意的是,形式参数也是局部变量,随方法结束一起消亡。
成员变量的说明
成员变量如果没有初始化,有其默认值:基本类型为其本身的默认值,引用类型的默认值为null
变量提升
变量提升即,在byte、short、char基本类型作运算的时候,会自动被提升为int类型的变量计算,得到的结果类型仍然为int
byte byte1 = 1;
short short1 = 2;
char c1 = '好';
// byte short char类型会自动提升为int类型进行计算
int result = byte1 + short + c1 ;
隐式转换
在变量赋值的时候,如果“=”左右两边是不同的基本数据类型,小的范围自动被隐式转换为大范围
// 左边范围大,隐式转换
long l1 = 1234; // int 转 long,隐式转换
// 这里用到了缓存机制,127在-128~127内,虽然说字面量式int,但是范围在缓存内,也可以式转换
byte byte = 127;
缓存机制
Java底层提供了数值类型的缓存机制,对Byte、Short、Integer、Long这四种包装类进行了数值的缓存
缓存何时生效?通过调用包装类的valueOf()方法时,会用缓存,以Byte为例
public final class Byte extends Number implements Comparable<Byte> {
// 略
private static class ByteCache {
private ByteCache(){}
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
// 这里用到了缓存
@HotSpotIntrinsicCandidate
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
}
Integer的缓存有些特殊,它可以通过JVM启动参数来设置
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
注意
只有Integer的缓存范围可设置,其他三个都是-128~127
Integer i1 = Integer.valueOf(123);
Integer i2 = Integer.valueOf(123);
// i1 == i2 地址是一样的,都为缓存的cache[i],但是如果超出该范围,就不是同一个地址了
直接赋值时,如果是字面量,会先去缓存中获取,缓存中没有时,才进行堆分配,因为valueOf方法是从cache中获得的值,只要在cache范围内,通过valueOf得到的Integer的变量内容的值都是指向一个值,而非引用地址
运算符
混合运算
-
按照运算符我优先级进行运算
-
如果有String类型参与运算,当到String优先级后,最后的类型都将转为String类型
-
最终结果以类型范围表示大的为结果
比如
int + long + double + float
结果为double
型 -
考虑变量提升问题
自运算
自增运算( ++
)或自减运算( --
)运算时,可能会造成范围溢出
byte abyte = 127;
abyte++; // 此时abyte 为-128
注意
如果是 +=
-=
*=
/=
自运算符时,不会改变等号左边的数据类型
int a = Integer.MAX_VALUE;
a += 1;
// 这里a还是int类型,然后产生了溢出
System.out.println(a);
逻辑与或非
逻辑与:&
,条件都要计算,无论是否有false返回
逻辑或:|
,条件都要计算,无论是否有true返回
短路与:&&
,只要有false返回,就不计算后面的条件
短路或:||
,只要有true返回,就不计算后面的条件
非:!
,取反,false取非为true;true取非为false
// 短路与或 str 为null
int a = 1;
int b = 2;
int c = -9;
if(a > b || c<b || str.equals("1") ){
System.out.println("1");// 短路或时,从左到右依次判断条件,当前仅当至少有一个条件为tre时,则返回,之后的条件不进行判断
}
if(a > b | c<b | str.equals("1") ){
System.out.println("1");// 正常的或,每个条件都要计算结果,则str会抛出空指针
}
//短路与
if(a > b && c<b && str.equals("1") ){
System.out.println("1"); // 短语与,从左到右依次判断条件,当遇到第一个false时,返回,后面条件不再进行判断
}
//正常与
if(a > b & c<b & str.equals("1") ){
System.out.println("1"); // 会抛出空指针,因为每个条件都要去判断是否为true
}
位运算
运算类型 | 描述 | |
---|---|---|
与 | & | 各个位作与操作,包含符号位 |
或 | | | 各个位作或操作,包含符号位 |
反 | ~ | 各个位取反,包含符号位 |
异或 | ^ | 位运算中相同为0,不同为1 ,包含符号位 |
左移 | << | 空位补0,被移除的最高位丢弃,空缺位补0 |
右移 | >> | 被移位的最高位是0,右移后,空缺为补0;最高位是1 空缺位补1 |
无符号右移 | >>> | 被移位的二进制最高位无论是0还是1,空缺位都用0补 |
进制和补码
/** 因为int有32位太长 因此,以byte类型为基准 8位
* 原码
* 举例:7
* 正数的原码、反码、补码一样
* 负数的原码:最高位符号位不变,其他位先减去1然后 按位取反
* 底层逻辑:计算机底层都是以补码形式保存的
*/
异或幂等
int num1,num2;
num1 = 10;
num2 = 20;
// 异或交换
/*
基于异或
令 k = m ^ n
m = m ^ n ^ n
n = m ^ n ^ m
*/
num1 = num1 ^ num2; // 先异或出一个结果
// num1此时,为 k , 如果k 异或num2,则得到的结果为num1的值,这就是num1赋值给num2了
num2 = num1 ^ num2;
// num2此时为num1的值
//num1还是那个k,则异或后,得到num2的值,左边时num1则,num2的值赋值给num1了
num1 = num1 ^ num2;
System.out.println(num1);
System.out.println(num2);
利用异或的幂等性,可以交换两个数值,而不借助第三个变量
循环
循环的要素包括:循环条件,循环体,初始化,迭代部分
循环分为三种:
- for循环
- whle循环
- do-while循环
各个循环的使用场景
- for循环,包括增强for循环,它适用知道循环次数的情况
- while循环,它使用不知道循环次数的情况,但是一定有一个终止循环的条件可以达到
数组
- 数组是一组相同类型的数据集合
- 访问速度快,通过索引下标直接访问
- 长度固定,不易扩展和扩容
Arrays工具类
提供了很多和数组相关的方法
例如:查找、排序、复制、比较、填充、Stream流相关等常用的方法,需要我们掌握这个类的使用
面向对象
这里主要将面向对象的一些概括,里面包括很多
内存结构
- 堆(heap):此内存区域的唯一目的就是存放对象实例(数组,引用类实例)
- 虚拟机栈:我们通常所说的栈,用于存储局部变量等,局部变量表存放了编译期可知长度的各种基本数据类型、对象引用(首地址)。方法执行完自动释放
- 本地方法栈:调用操作系统的C库或其他库
- 方法区:用于存储被虚拟机加载的类信息、常量池、静态域、即时编译后的代码等数据
局部变量和成员变量
局部变量是方法内的变量,存在局部变量表内
形式参数也是局部变量,就解释了为啥String传入不可变
属性:加载到堆空间(非static,static放入方法区内)
局部变量:加载到栈空间,方法传参时是值传递
值传递
在Java中,参数只有一种传递方式:值传递!引用传递也是传递的地址值!
在实际调用时,方法参数的值是实际参数值的副本
- 如果参数是基本类型,直接拷贝数据值
- 如果参数是引用类型,会将实际参数的地址值拷贝给形式参数
包和导入
package 按照项目和模块分级,声明在类的最前面
import 导入的使用
- 显示导入指定结构下的接口、类
- import xxx.xx.*表示指定结构下的所有类或接口,
*
可以理解为通配符 - 如果用到本包的其他类,不需要用import显示导入
- 如果用到子包下,则需要import显示导入
- Java.lang下的结构不需要导入,默认可以使用
封装性
封装是为了追求高内聚、低耦合的目标,封装了类内部的操作细节,不需要外部干涉。隐藏实现细节,对外公开简单的接口,从而提高系统的可扩展性、可维护性
有哪些体现?
- 私有属性,公共getter/setter
- 单例模式,构造器私有化,提供公共的得到类的实例的静态方法
- 不对外暴露的私有方法
- 静态内部类
- 权限修饰符,它仅仅只是封装性的表现
继承性
目标:减少代码冗余,提高可扩展性,为多态提供前提
一旦子类B继承了父类A,那么子类B种就拥有了父类A种声明的结构(属性、方法)
private修饰的父类成员变量,子类继承了吗?
实际上,是继承了的,由于封装性的影响,不能在类外部直接访问,最直接的就是通过反射进行访问
public class Person {
// 父类声明的私有成员
private String name;
private void methodA(){
}
}
class Student extends Person{
// 实际继承了name 和 methodA
// 由于封装private的影响,不能再外部直接调用,
}
多态性
理解:一个事物的多种形态
对象的多态性:父类的引用指向子类的实现
方法的多态:对象多态调用方法多态(因为基于继承的特性)
属性多态和方法多态
比如两个类Person、Student
class Person{
private String name;
void eat(){
System.out.println("Person eat");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Student extends Person{
private String name;
void eat(){
System.out.println("Student eat");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 测试方法
public static void main(String[] args) {
Person person = new Person();
Student student = new Student();// 动态绑定
person.eat(); // person eat
student.eat(); // student eat
Person p2 = new Student(); // 子类实现
p2.eat(); // student eat 方法的多态性
}
// 但是属性的调用就是看左边
public static void main(String[] args) {
Person person = new Person();
Person p2 = new Student(); // 子类实现
p2.name; //person类的name
Student stu = new Student();
stu.name;// student的name属性
}
在子类Student的实例对象内存中,有自己的name;而父类Person的实例对象中,也有自己的name,它们是不同的,但是在多态调用中
- 如果是方法,则运行时确定(动态绑定)
- 如果是属性,则编译时确定(静态绑定)
多态性使得面向接口编程得以发展,编程更加规范
虚拟方法调用
虚方法调用(Vitural Method Invocation),即多态方法调用
正常方法调用,声明的类型和实现子类一致,这在编译器是确定下来的
Person p = new Person();
Student stu = new Student();
p.eat(); // person的eat
stu.eat(); // stu的eat
虚拟方法调用(多态情况下),动态绑定
// 编译时确定 p类型,但具体的实现是在 运行时确定的 ,所以又称 动态绑定
// p.eat的方法就是student的eat方法了(前提时student覆写了父类person的方法eat)
Person p = new Student();
p.eat();
实例化过程
- 产生对象并初始化成员变量的默认值
- 对构造方法中的形式参数进行赋值
- 构造方法是否有this调用,如果有,走this调用,但至少有一个this是有super的调用的。知道追溯到Object的构造为止
成员属性(非静态)赋值的顺序:
①默认初始化
②显示初始化
③构造初始化
④对象.方法/对象.属性
对象实例化过程
- 从结果上看:子类继承父类以后,就继承了父类声明的属性和方法。创建子类的对象,在堆空间中就会加载父类声明的属性和方法
- 从过程上看:子类继承父类后,创建子类时,会直接或间接地调用父类构造器。正因为加载过父类的结构,才可以看到内存中有父类的结构,所以子类才可以调用这些属性
注意
虽然创建子类对象时,调用了父类构造器,自始自终就只创建了一个对象;调用构造器并不是new关键字开辟的空间
子类实例化时,不会创建父类对象。
向上转型
- 子类对象转换成父类对象
- 子类需要和父类是接口实现关系或继承关系
向下转型
父类对象转换成子类对象,由于继承性,子类可能扩展了父类的功能,因此向下转型是不安全的
但是,如果没有多态的支持,向下转型是不成功的,为什么向下转型,因为该对象要用到子类的特性(本身就是动态绑定的子类)
A a = new B();
// 调用没有被B重写过的方法methodOfA()
a.methodOfA();
// 想要用B的独有方法了 向下转型
B b = (B)a;
// 调用B独有的方法,此时就是基于动态绑定的特性
b.methodOfB();
当然在向下转型的时候,最好通过 instanceof
关键字来判断是否是子类的实例
Object类
Object类是所有类的默认父类,如果没有显示的extends,则默认的继承Object
方法名 | 返回类型 | 方法描述 |
---|---|---|
clone() | Object | 复制一个对象,但是类要实现cloneable接口才可以,是一个浅拷贝 |
equals(Object obj) | boolean | 对象比较方法,默认比较的是地址的hash值 |
finalize() | void | 调用垃圾回收器,我们不要自己去调用 |
getClass() | Class<?> | 返回该对象所属类的字节码对象(Class对象) |
hashCode() | int | 返回该对象的hashcode值 |
notify() | void | 唤醒一个在该对象监视器上等待的线程 |
notifyAll() | void | 唤醒所有在该对象监视器上等待的线程 |
toString() | String | 返回该对象的字符串表示 |
wait() | void | Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. |
wait(long timeout) | void | Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed. |
wait(long timeout, int nanos) | void | Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed. |
static关键字
可以修饰:属性、方法、代码块、内部类
-
修饰属性:类中所有对象共享属性、可以通过
类.静态成员
进行调用或实例对象.静态成员
静态变量在JVM方法区内的静态域中,而实例变量在堆中保存,静态变量随类加载而加载
-
修饰方法:静态方法,可以通过
类.静态方法
的形式进行调用,但是在静态方法里面,只能调用静态的方法和静态属性。且不能使用this和super关键字 -
修饰代码块:静态代码块,执行完静态属性初始化完成后。类似于构造块的感觉
final关键字
-
final修饰类,表示不可继承
-
final修饰方法,表示该方法不可复写
-
final修饰成员,表示该成员最多赋值一次,且不可修改。
-
final修饰方法内的变量,表示不可修改,最多赋值一次
abstract关键字
abstract可以修饰类、方法
-
修饰类:该类为抽象类,不能直接实例化,但是类必须提供一个构造,以便继承的子类进行实例化
-
修饰方法:该方法为抽象方法,没有实现,具体的实现有子类进行实现,子类继承了必须实现抽象方法。
关于抽象类说明
- 继承抽象类的类,如果有抽象方法,则该类必须为抽象的;
- 抽象类可以没有抽象方法,不妨碍被abstract修饰;但是有抽象方法声明就要使用abstract关键字
- 抽象类起到了很好的模板作用,可以让子类少实现方法
interface关键字
接口,更高级的抽象。接口是一种规范,就是定义的规则;接口和类是两个并列的结构
特点:
- 可以多实现,间接达到了多继承的效果
- 实现接口必须实现接口里的所有方法,但是子接口可以不用实现
- 接口有默认的方法default
- 接口可以定义static常量
- 接口中不可以有构造器
JDK7以前
- 接口内只能定义全局常量:public static final
- 接口内只能定义抽象方法:public abstract 方法
JKD8
- 可以定义静态方法 static方法
- 可以定义默认方法 default 方法
匿名实现类
什么是匿名实现类,不妨这样假设:有一个抽象(接口),现在我们要去继承(实现)这个抽象类(接口),那么我们又不想新定义一个类,直接在代码中定义,类似于这样
抽象类/接口 变量 = new 抽象类/接口(){
// 抽象类继承子类的定义或接口的实现定义
// ...
}
new
关键字后面的是抽象类或接口,但是,它的实现是在 {}
内定义的,所以是匿名类
但是这个变量是一个有名字的变量,因此它不是匿名的,而是匿名实现类的有名字的对象
匿名对象
顾名思义,匿名的就是没有一个变量或类来接应它,即直接new出来,而不赋值给任何东西。因此是匿名的,只可以使用一次。
new 类(); //类是可以实例化的类,可以是匿名实现类
代码块
static修饰的{}为静态代码块,在类静态字段初始化后执行,也是类加载的时候执行,仅仅执行一次,可以有多个静态代码块,按顺序执行!
{}声明在类中不加任何修饰的为构造块,先于构造方法执行,可以有多个构造块,按顺序执行
如果声明在方法内,为普通代码块,内部的变量随代码块结束而消亡
内部类
- 成员内部类,由static 和非 static修饰的class
- 局部内部类(无修饰符),匿名内部类
什么时候使用内部类,一般成员内部类在各个框架用的比较多,主要是服务于它的主类。如果用static修饰,相当于外部类,但是调用的时候只有在主类内部才可以调用其方法
异常
Java在执行过程中所发生的中断可分为两类
-
Error
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重问题,比如StackOverflowError和OOM,一般不编写针对性的代码处理。
-
Exception
其他编程错误或偶然的外在因素导致的一般性问题。可以使用针对性的代码进行处理,例如:
空指针访问、文件未找到、网络连接中断、数组下标越界等
异常的分类
-
编译时异常(也称受检异常)
在编写代码时,可以出现的异常,必须被处理或者向上抛出
-
运行时异常(非受检异常)
在编译器无法确定是否出现,当运行时可能会出现,比如数组下标越界异常等。
异常的体系结构
java.lang.Throwable
|----java.lang.Error
|----java.lang.Exception
|----java.lang.RuntimeException
我们RuntimeException时运行时异常,可以不用捕获,出现时自动抛出,但是其他受检异常必须捕获或抛出
throws 和 throw
throws 用在方法上,声明可能产生的异常类型,需要调用者去捕获异常并处理,而throw 是手动抛出异常
定义异常
我们可以继承Exception类或者RuntimeException类
高级部分
多线程
要是服务于它的主类。如果用static修饰,相当于外部类,但是调用的时候只有在主类内部才可以调用其方法
异常
Java在执行过程中所发生的中断可分为两类
-
Error
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重问题,比如StackOverflowError和OOM,一般不编写针对性的代码处理。
-
Exception
其他编程错误或偶然的外在因素导致的一般性问题。可以使用针对性的代码进行处理,例如:
空指针访问、文件未找到、网络连接中断、数组下标越界等
异常的分类
-
编译时异常(也称受检异常)
在编写代码时,可以出现的异常,必须被处理或者向上抛出
-
运行时异常(非受检异常)
在编译器无法确定是否出现,当运行时可能会出现,比如数组下标越界异常等。
异常的体系结构
java.lang.Throwable
|----java.lang.Error
|----java.lang.Exception
|----java.lang.RuntimeException
我们RuntimeException时运行时异常,可以不用捕获,出现时自动抛出,但是其他受检异常必须捕获或抛出
throws 和 throw
throws 用在方法上,声明可能产生的异常类型,需要调用者去捕获异常并处理,而throw 是手动抛出异常
定义异常
我们可以继承Exception类或者RuntimeException类