开始
Java代码的运行原理
编码军规
Java编码规范:
- 小驼峰: 第一个单词全小写,后续单词第一个字母大写(项目名,包名,变量(对象)名,方法名)
- 大驼峰:所有字节(单词)的第一个字母大写(类名,接口名)
- 常量定义规范:所有字母都大写,不同字节用下划线分割(常量对象,局部常量,常量属性)
- 包命名的规范:com.公司名.项目名.功能包名
- 变量、属性的声明:不允许一行声明多个变量
- 运算符、赋值号与标识符或者常量之间有一个空格的间隔
- 注释
- 单行注释使用位置:变量、属性、简单语句逻辑的功能描述
- 多行注释:描述方法内部,较复杂的逻辑
- 文档注释:描述方法、类、接口、枚举的 功能或者参数、注意事项描述
- 注意:注释独立其所在的行,不能和代码在同一行。
- 杜绝魔鬼数字:对于代码中所有的多次使用的同一类角色的数值以及字符串的字面量,称为魔鬼数字。
- if或者for或者while语句块即使只有一条语句,也需要使用 { } 包含,增加代码可读性。
- 方法规范
- 代码行数不宜于过长:48行左右
- 方法中逻辑分支小于 7: if语句、循环语句等所有的语句块结构小于7层(包含 && 和 ||)
- 指标:称为逻辑复杂度(全复杂度或者 圈复杂度)
Java基本语法
变量:
存在于栈中,生命周期:定义其的花括号内
变量的内存原理图
int x = 10;
基本数据类型:
内存中最小的逻辑存储单元,其他数据类型都需要分解为基本数据类型来存储
整数 | 小数(浮点数) | 非数值 | |||||
---|---|---|---|---|---|---|---|
byte | 1B | float | 4B | char | 2B | ||
short | 2B | double | 8B | boolean | 1B | ||
int | 4B | ||||||
long | 8B |
引用数据类型:
栈中的变量名存储的是值的引用地址,而非值本身,并且需要分解内容为基本数据类型来存储
运算符的优先级
类型自动转换和强制转换
小转大自动转,大转小强制转换
注意:大转小有可能失去精度,小转大虚线时也有可能失去精度
数组和字符串
数组:
存放一组有意义的值或者对象
数组对象的特点:一旦定义,长度不可变化
所有非空数组对象.length : 表示数组元素个数
注意: == 如果判断基本数据类型是比较值是否一致, 如果判断引用数据类型则比较的是地址(同一个对象地址才一样)
定义
- 类型[] 数组名 = new 类型[int类型的数量]
- 类型 数组名[] = new 类型[int类型的数量]
- 类型[] 数组名 = new 类型[]{ 枚举值1, 枚举值2 … }
数组内存原理图
二维数组:
一维数组的数组,二维数组的每个元素是一维数组,一维数组每个元素是具体的值
二维数组内存原理图
int[][] a = { {1, 2, 3},{4, 5, 6} }
练习
-
定义一个数组:将1~100以内的所有素数(质数:只能被1和自己本身整除的数)存入
-
双色球机选程序(定义一个数组:存储双色球的红色球,升序排序并输出红色球,显示蓝色球) 红色球:1~33 随机选6个 蓝色球:1~16 随机选1个
-
定义矩形数组,按照如下规则进行填值 1 2 3 4 5 6 12 11 10 9 8 7 13 14 15 16 17 18 24 23 22 21 20 19 -
给正方形数组按照如下规律填值 1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7
字符串
字符串特点:一旦定义不可变更(长度和内容都不能变更)
字符串常量的特点:没有就会在常量池中定义,如果有,则直接引用。
字符串内存原理图
构造字符串对象:
- String s1 = new String(“abc”);
- String s2 = new String(new char[]{‘a’,‘b’,‘c’});
- String s3 = “abc”;
StringBuffer、StringBuilder、String的区别:
-
String:是不可修改的字符序列,所有的方法都不能修改其自身的内容和长度
对只读的字符序列String的读取性能高于StringBuffer,以及StringBuilder;
使用场景:只读的字符序列
-
StringBuffer、StringBuilder: 都是可变的子串缓冲区,本质是用char[] 存储字符序列,
内容、长度都可变化。优点相对于 String 可变化,缺点对字符序列读取性能差
使用场景:代码中频繁修改的字符序列
-
StringBuffer是线程安全的,StringBuilder是非线程安全
类、对象和方法
类和对象的关系:
层次1:
类:具体事物的抽象表示
对象:某一类事物的一个具体的事务
层次2:
类:对象的模板
对象:由模板(类)生成的具体的事务,目的是解决问题
先后关系:先有类,再用类生成对象
类的最终目标:生成对象
对象的最终目标:解决问题
层次3:
类:自定义的类型
对象:自定义类型生成的变量
对象内存原理图
类的定义
属性:记录对象信息的空间(数据结构) 数据存储空间
方法:方便对象的功能实现 操作数据空间(数据)的动作(行为)
方法的调用机制:
1- 哪个对象调用这个方法,方法中的属性,就是哪个对象的(就用哪个对象进行寻址)
2- 在哪里调用方法,方法的结果返回到哪里
C语言,什么是程序:算法+数据结构
面向对象语言中,什么是程序: 对象的集合
对象属性:数据结构
方法:算法
/*
* 构造器
* 1- 构造器必须和类名完全一样,包括大小写
* 2- 构造器不能有任何返回类型
* 作用:在底层寻址开空间
* 构造器和普通方法的区别
* 1- 定义不同
* 普通方法必须有返回类型,方法名遵循小驼峰命名规范
* 构造器不能有返回类型,构造器名必须和类型一致
* 2- 意义不同
* 普通方法是:方便对象的使用,没有也可以
* 构造器:是给对象开空间(初始化对象在堆中的空间地址;
* 当不定义的时候自动添加一个无参构造器;
* 如果定义了构造器,则编译器不会自动生成其他构造器)
* 3- 调用时机和方式不同
* 普通方法:生成对象之后(堆中有属性地址被开辟后),再由对象.普通方法名();
* 构造器:先对对象实例化(在堆中开辟属性空间),调用方式: new 构造器();
* */
类成员的访问控制
栈和队列
练习:
- 非循环队列
- 循环队列
- 栈
继承
static
static属性和非static(成员属性)的区别
- 所属不同:
- 成员属性:属于对象,必须由对象.属性 才能去堆中寻址得到并操作
- 静态属性:属于所有对象,所有对象共有的空间,可以由对象调用,也可以由类直接调用
- 生成时机不同
- 成员属性:由构造器,实例化具体对象是构造
- 静态属性:由类加载时进行开辟空间(及时没有定义任何对象,都会产生)
static方法和成员方法的区别:
- 被调动方式不同
- 成员方法:只能由对象调用
- static 方法:可以由对象调用,也可以由类直接调用
- 调用限制
- static方法:只能调用static的属性和方法,不能调用成员的属性和方法
- 原因:方法调用机制——哪个主导调动方法,则方法中的属性就是哪个主导的;所以当类直接调用static方法,无法主导其内部的成员信息(属性、方法)
- 成员方法:没有限制,成员方法中可以调用成员的信息,也可以调用静态的属性和方法
- static方法:只能调用static的属性和方法,不能调用成员的属性和方法
成员语句块
成员语句块:执行时机(在构造对象时调用)
作用:可以初始化实例对象信息
{
System.out.println("成员语句块");
this.name = "测试";
classMoney += 10;
}
静态语句块:执行时机(加载类时)
作用:初始化类属性信息
static {
System.out,println("static 语句块")
}
代码语句的执行顺序:
- static语句块
- 成员语句块
- 构造器语句块
继承
相当于代码拷贝(子类拷贝父类的代码)
子类可以直接使用父类定义的方法,就像使用自己的方法一样,属性也是如此;但是也需要遵循类之间的访问规则(子类代码不能直接调用父类私有属性和私有方法)
继承的规则
自定义的每个类有且只能有一个父类,如果编码时不写继承,则默认继承 java.lang.Object 类
构造器的调用机制
哪个类定义的属性空间,只有哪个类的构造器有能力和义务去开空间;
动态绑定:父类对象获取了子类对象的引用
注意:父类对象只有得到同类的子类对象引用,才能强制转换为这个子类的对象,否则ClassCastException异常
父类对象 = (子类类型) 父类对象
多态
当父类对象获取了子类对象的引用后,调用的是子类的覆盖方法,而不是父类的被覆盖方法。
抽象类的特点:
不能调用自己的构造器生成对象
抽象类的意义:
天生的父类,不用来实现对象,只用来规则子类
方法覆盖(重写)
包和接口
快排
接口
public interface Inter {
// 属性:public static final
// 使用场景:定义项目中的全局参数
public static final TEPE_VAL = 10;
// 方法:public abstract
public abstract int getType();
}
接口的使用场景
- 用来定义一些项目的全部参数
- 定义抽象方法,让类进行试下,从而实现多态
使用 Compareable 接口
1- 模型类接入 Compareable接口
2- 实现 compareTo方法
写比较算法:
return 1 交换位置
return -1 不交换位置
例如: this > obj return 1 否则 return -1 就是升序排序
this < obj return 1 否则 return -1 就是降序排序
3- 调用排序算法 Arrays.sort(实现了Comparable接口的数组对象)
缺点:
- 不能有null对象
- 有代码侵入(要在模型类上实现接口compareable接口的数组对象)
使用 Cloneable接口:
对要实现克隆的模型类:
-
覆盖Object的clone方法
@Override public Object clone() throws CloneNotSupportedException { //浅拷贝:仅仅拷贝基本数据类型属性 Object result = super.clone(); //深拷贝:手动建立引用数据类型空间,将原有的值赋值到新空间 Student stu = (Student)result; stu.setName(new Name(stu.getName().getFirstName(), stu.getName().getLastName())); return stu; }
-
实现 CLoneable 接口
public class Student implements Cloneable{ ... }
标准类库
java.util.Date 表示一个瞬态时间,封装了从 1970-1-1 00:00:00 到指定时间的毫秒数 构造器: new Date(): 构造一个当前时间点的瞬态时间对象 new Date(long类型的毫秒数): 构造一个从1970-1-1 00:00:00到指定时间的毫秒数 常用方法: date.getTime(): 返回date距离1970-1-1 0:0:0 的毫秒数 date.setTime(long类型毫秒数): 设置date对象的时间信息(从1970-1-1 00:00:00到指定时间的毫秒数的时间信息) java.util.GregorianCalendar 表示一个日历对象的封装类 java.text.SimpleDateFormat yyyy 4位的年 MM 2位月 dd 2位日 HH 24时制 mm 分钟 ss 秒
异常
异常处理机制
异常处理机制:
1- 将认为有异常的代码放入 try 代码段,在其后的catch代码段中捕获异常
2- 因为try中可能有多个异常,所以可以跟多个catch捕获不同的异常信息(异常信息封装的对象被catch定义的异常对象捕获到)
3- 如果try中代码没有异常,则任何catch不会被执行;如果try代码段有异常,而没有与之匹配的catch,则程序异常结束。
4- 对unchecked异常不强制使用 try catch 异常处理;对checked异常强制使用try catch语句块(如果不使用,会编译错误)
5- 如果出现异常,catch代码段会按照顺序(从上到下顺序)捕获,只有一个catch捕获到异常后执行,其他catch被忽略。
6- 父类异常catch要放在子类异常catch以后(catch中的类型必须为 Throwable范围:Throwable类及其子类)
7- finally代码段放在catch之后,作为程序执行的最后的安全屏障,无论什么情况下都执行其代码段。 finally代码段中会关闭通道(外部设备的通道);保存安全数据等安全事务处理;
异常分类
异常的再抛出
异常的再抛出:
如果有异常,JVM会抛出,但是方法获取了异常自己不处理,再抛出给调用此方法的位置,就称为异常的再抛出
抛出方式:
1- 显性再抛出:在方法签名上(形参列表和方法体之间的位置)添加上 throws 异常类型1,异常类型2 ...
例如:
public static void method3() throws ArithmeticException, ArrayIndexOutOfBoundsException{
...
}
2- 隐性再抛出:不用在方法签名上声明再抛出的异常类型列表,有异常由JVM自己进行再抛出。
注意:只有unchecked(运行时异常)异常可以进行隐性再抛出;checked(非运行时异常)如果方法自己不处理,只能使用显性再抛出;
覆盖有显性再抛出checked异常的方法时, 子类的覆盖方法可以不进行再抛出(意味着,子类覆盖方法的方法体能处理所有的异常)
如果子类覆盖方法要显性声明,则范围一定要小于父类被覆盖发发的checked异常范围,否则编译错误; 因为多态:父类的方法签名要能处理子类方法体出现的异常,所以要求子类显性声明的异常范围要小于父类签名范围;
throw和throws的区别
throw是手动抛出的关键字, 抛出格式:throw 异常对象
throw是显示声明异常的再抛出类型列表的关键字:
public static void method() throws 异常类型,异常类型, ...{
...
}
Integer
会封装一些常用值的对象数组:IntegerCache.cache[], 仅仅是 -128~127的数值
-128 | -127 | -126 | -125 | … | 123 | 124 | 125 | 126 | 127 |
---|---|---|---|---|---|---|---|---|---|
Integer[0] |
集合
遍历
Iterator
Iterator it = list.iterator();
while(it.hashnext()) {
if(it.next().equals("d")) {
it.remove();
}
}
Collection
ArrayList和LinkedList区别
ArrayList和LinkedList的区别: | |
---|---|
1- 管理数据结构不同: | |
ArrayList用数组结构管理对象 | |
LinkedList用链表结构管理对象 | |
2- ArrayList遍历比较快 | |
LinkedList对元素进行频繁添加和删除效率较高 | |
3- 使用场景: | |
从数据库提取的数据,用来遍历,很少修改,用ArrayList居多 | |
算法中,需要频繁添加和删除元素的用LinkedList居多 |
Set接口下的类:无序,访问速度快;可以添加一个null对象 |
SortedSet接口下的类:先天排序,访问速度慢;不能添加null对象 |
注意:如果要覆盖 hashCode和equals 则都必须同时覆盖,否则无效 |
Map
Map接口的实现类,key可以为 null |
SortedMap接口实现类,key不能为 null |
泛型
泛型类(接口):当实例化对象时才确定其属性或者方法的类型
泛型预定类参数:
1- 一个大写字母
2- 存放一系列元素则优先使用 E (Element的首字母)
如果表示一个键,优先使用 K(Key)
如果表示一个值,优先使用 V (Value)
除了上述三种情况以外,优先使用 T, U, S
泛型的擦除:不设定泛型的具体类型,由JVM指定其类型
1- 擦除没有任何限定的泛型类为 Object
例如: public class MyValue<T, U> { ... }
new MyVal(); T和U都被擦除为 Object
2- 擦除有一个类的限定时,默认被擦除成限定的类
3- 擦除有一个接口的限定时,默认被擦除为限定的接口
4- 擦除有多个接口的限定时,只擦除为第一个接口(最左边的接口)
public class MyValue<T, U extends Inter1 & Inter2> {
U 默认擦除为 Inter1 的类型
5- 擦除有一个类, 且多个接口的限定时,只擦除为这类的限定,舍弃接口限定。
public class MyValue<T, U extends Parent & Inter1 & Inter2>
U 的类型被擦除为Parent类型
泛型的限定
泛型的限定:预设的类型必须为某一个类或者其子类(接口)时需要限定
1- 添加一个类的限定,限定为这个类或者其子类类型:
例如: public class MyValue<T, U extends Parent> { ... }
U 类型 被限定为 Parent类及其子类
2- 添加一个接口的限定,限定这个类型为接口或则其实现类或者子接口
例如: public class MyValue<T, U extends Inter1>
U 类型 被限定为 Inter1 接口或者其子接口或者其实现类
3- 添加多个接口的限定,限定这个类型为 同时实现了所有限定接口的类或者同时继承多个接口的接口
public class MyValue<T, U extends Inter1 & Inter2> {
U 的类型 被限定为要同时实现 Inter1和Inter2的类,或者同时继承 这两个接口的子接口
4- 添加有一个类,且有多个接口的限定(注意:类必须放在首位:最左边)
public class MyValue<T, U extends Parent & Inter1 & Inter2>
U 的类型 被限定为继承Parent并且实现 上述多个接口的类型
注意:因为Java不允许多重继承,所以没有多个类的限定
泛型的统配符 ?
JVM执行时 ? 被解析为Date类型
List<?> List3 = new ArrayList<Date>();
通配符的限定 ? extends 类或者接口
List<? extends Parent> List3 = new ArrayList<Son>()
? 通配符被 限定为 Parent及其子类
注意:目前通配符只支持一个类获取一个接口的限定
格式化输出、正则表达式
String str="029-97669779";
boolean result=str.matches("0\\d{2}\\-[1-9]\\d{7}");
内部类(嵌套类,局部类)
嵌套类 Nested class
Nested class: 嵌套类(类中定义类,和属性方法一个级别)
在外部类内部调用 Nested class:
在方法中实例化一个 Nested class 的对象,来使用
在外部类外部,调用 成员Nested class:
外部类名.嵌套类名 对象名 = 外部类对象.new 嵌套类构造求();
OuterClass.NestedClass nc = outer.new NestedClass(50);
OuterClass.NestedClass nc2 = new OuterClass(1).new NestedClass(50);
调用 静态Nested class
外部类名.嵌套类名 对象名 = new 外部类名.嵌套类构造求();
OuterClass.StaticNestedClass nc3 = new OuterClass.StaticNestedClass(100);
在成员嵌套类中使用外部类的this: 外部类名.this
注意:成员嵌套类不能定义 static属性和static方法; 静态嵌套类可以定义静态的属性和方法
因为加载外部类时,当遇到成员嵌套类中的静态属性和方法需要分配空间,而成员嵌套类在外部类对象出现是才能加载,所以加载空间时序性矛盾
嵌套类的意义:一个复杂的方法,复杂到需要自己定义数据结构(属性),和不同的方法(嵌套类自定义方法)来完成一个功能,让外部类从外部看起来是一个算法整体。
局部类 Inner class
方法中定义类,和形参一个级别
Inner class 可以使用成员属性,但是不能使用变量形参(可以使用 final 修饰的形参)因为加了final的空间会出现在常量池中,只要有引用就不会注销,
而变量,只存活定义其的花括号里面,出去空间就会被注销
匿名类(匿名嵌套类和 匿名局部类)
使用场景:对于只使用一次的类,仅仅定义一个对象来完成功能。
使用格式:
父类/接口 对象名 = new 父类/实现接口() {
// 匿名类体
}
多线程
线程对象的运行状态
实现同步操作的两种基本方式:(使用synchronize 关键字)
-
1- 功能写在线程中,使用同步语句块实现同步
-
2- 功能写在资源对象中,使用同步方法实现同步
资源对象:线程为之共工作的数据对象
互斥资源对象:多个线程都需要的为之工作的对象
-
两种方法的优缺点:
同步语句块:
// 优点:功能在线程中,可以嵌套形式锁定多个资源对象
synchronized(obj1){
synchronized(obj1){
//同时锁定了 obj1, 和 obj2 进行功能实现
}
缺点:功能书写分散(分散在不同的线程总,可能出现代码冗余)
同步方法:
优点:功能都卸载资源对象的不同方法中,比较集中完成功能操作。
缺点:由于方法属于一个资源对象,所以无法直接实现同时锁定多个资源下的复杂功能
唤醒阻塞中的,由 当前资源对象.wait() 的 随机唤醒某一个线程
this.notify();
唤醒阻塞中的, 由 当前资源对象.wait() 的 所有线程
this.notifyAll();
使用wait和notify、notifyAll的限制
1- 认识wait 和 notify、notifyAll:
都是Object的方法,所有对象都有
2- wait的作用,资源对象调用.wait方法,线程对象去调用“资源对象.wait()”这条语句,则线程被挂起
3- notify的作用,资源对象调用.notify(),则只有被此资源对象wait的线程会被唤醒
- 2- 功能写在资源对象中,使用同步方法实现同步
资源对象:线程为之共工作的数据对象
互斥资源对象:多个线程都需要的为之工作的对象
-
两种方法的优缺点:
同步语句块:
// 优点:功能在线程中,可以嵌套形式锁定多个资源对象
synchronized(obj1){
synchronized(obj1){
//同时锁定了 obj1, 和 obj2 进行功能实现
}
缺点:功能书写分散(分散在不同的线程总,可能出现代码冗余)
同步方法:
优点:功能都卸载资源对象的不同方法中,比较集中完成功能操作。
缺点:由于方法属于一个资源对象,所以无法直接实现同时锁定多个资源下的复杂功能
[外链图片转存中…(img-BV8a9eXG-1658828500681)]
唤醒阻塞中的,由 当前资源对象.wait() 的 随机唤醒某一个线程
this.notify();
唤醒阻塞中的, 由 当前资源对象.wait() 的 所有线程
this.notifyAll();
使用wait和notify、notifyAll的限制
1- 认识wait 和 notify、notifyAll:
都是Object的方法,所有对象都有
2- wait的作用,资源对象调用.wait方法,线程对象去调用“资源对象.wait()”这条语句,则线程被挂起
3- notify的作用,资源对象调用.notify(),则只有被此资源对象wait的线程会被唤醒
4- 必须在资源对象自己的同步语境(同步方法、同步语句块),才能有这个资源对象去调用wait和notify方法。否则:java.lang.IllegalMonitorStateException