Java基础知识杂记
Java杂记
静态方法无法继承 无实例化对象
异常
数组索引异常
空指针异常
算数异常
丢失资源
找不到类
编译时异常
oop 面向对象的语言
对象的成员属性在未赋值前
引用类型的默认值是NULL
见到那类型为对应的0
通过this可以访问当前对象的成员属性/成员变量(静态的成员变量不支持)
默认
int - 0
float - 0.0f
char - ‘\u0000’
boolean - flase
构造方法
非特殊的一个方法:
1.方法名必须和类名相同
2.没有返回值
3.创建对象时由编译器自动调用
当一个类中没有任何一个构造方法时 java会自动提供一个无参的构造方法
构造方法之间可以形成方法的重载
1.方法名相同
2.参数列表不同
构造方法只对对象中的成员进行初始化 不进行空间分配
完成一个对象的构造
1.分配内存
2.调用合适的构造方法
this();调用当前类当中其他构造方法
只能在当前的构造方法内部进行使用
只能放在第一行
this.data访问当前对象的属性
this.func()调用当前对象的方法
this本身代表当前对象的引用
就地初始化
声明成员变量的同时进行初始化
即在class内进行初始化
面向对象的三大特性
封装 private修饰
是数据和操作数据的方法进行有机结合 隐藏对象的属性和实现细节 仅对外公开接口来和对象进行交互
对类的成员 进行隐藏 通过关键字private 只是对类对外提供公开的接口
意义
可以隐藏的实现细节 从而达到安全性
继承
多态
包
更好的管理类
多个类收集成为一组 对类或接口很好的组织方式
对类、接口等封装机制的体现
非静态成员变量属于对象 访问方式 对象的引用.xxx
静态成员变量的使用 不依赖于对象(不用实例化 【new】)类名.xxxx
静态成员变量(类变量)
静态方法 (类方法)
静态方法内部不能直接调用非静态方法/成员变量
static方法中不能使用this
需通过对象的引用调用静态方法
可以通过类名进行访问
调用静态方法不需要实例对象
实例
静态代码块>实例代码块>构造代码块
静态代码块只执行一次
实例代码块一定是实例化对象的时候被执行
在java中类的静态变量会被默认初始化
构造方法中形参名和成员变量名相同 需要用this指定
在类外使用需要使用public访问修饰符
static只能修饰成员变量 不修饰局部变量
继承
当子类和父类是同名成员变量 优先访问子类
访问父类->super关键字
this访问既可以访问父类也可以访问子类成员变量 同名时 优先子类
super只能访问从父类继承过来的成员变量
super在非静态的方法中使用
super和this再构造方法调用时 都只能放在第一句 且不能同时出现
在不同包中访问父子间关系 protected
main方法为静态的 所以先创造一个构造方法
一个类不想被继承使用final关键字修饰
1 | public final class Test{ |
继承 is-a的关系
组合是 has-a/a part of 的关系
多态实现条件
向上转型
1.直接赋值
2.方法传参的方式
3.返回值
通过父类引用 调用这个父类 子类重写的方法
子类和父类 有同名覆盖/重写
通过父类对象 调用父类和子类重写的方法
满足上述3点 只是会发生动态绑定
重写/覆盖规则
方法名一样
参数列表一样 (类型 个数 顺序)
返回值一样
重写的注意事项
1.被private修饰不可重写
2.被static修饰 属于类不属于方法
3.被final修饰的方法不可重写
4.访问修饰限定符 private<默认权限<protected<public(子类的访问修饰权限一定要大于等于父类的)
5.方法的返回值 可以不同 但必须是父子类关系(斜变类型)
6.构造方法不能发生重写
重载
方法名相同
参数列表不同(个数 类型 顺序)
返回值不同(可有可无)
代码的执行顺序
1.执行父类和子类的静态(先父后子)
2.父类的实例
3.父类的构造
4.子类的实例
5.子类的构造
1.抽象类是被abstract修饰的
2.被abstract修饰的方法成为抽象方法 该方可以没有具体实现
3.当一个类中含有抽象方法的时候 该类必须使用abstract修饰
4.抽象类中可以有和普通类一样的成员变量 一样的成员方法
5.抽象类不可被实例化
6.抽象类 目的就是为了被继承
7.当一个普通的类 继承了抽象类之后 该普通类一定要重写抽象类当中的所有抽象方法
8.fina、privatel和abstract不能同时存在(fina、privatel修饰一定不能重写 abstract修饰一定要重写)抽象类不能被static修饰
9.当一个抽象类A不想被一个普通类B继承 此时可以把这个类变成抽象类 当一个普通类C继承抽象类B之后 C要重写B和A当中所有的抽象方法
10.当一个类实现接口当中的方法之后 当前类当中的方法必须加public
抽象类中不一定包含抽象方法 但有抽象方法的类一定是抽象类
抽象类中可以有构造方法 供子类创建对象时 初始化父类的成员变量
11.接口当中不能有构造方法和代码块
12.一个接口也会产生独立的字节码文件 (编译之后.class)
13.类没有实现接口中的所有方法 那它必须设置为抽象类
匿名对象的缺点 每次使用都得重新实例化
接口
1.使用interface定义接口
2.接口当中的成员变量 默认是public static final
3.接口当中的成员方法 默认是public abstract 一般情况下不写
4.接口当中不可以有普通的方法
5.java8开始 允许在接口中定义一个default方法 修饰普通类
6.接口当中的方法若被static修饰的方法 可以有具体的实现
7.接口不能通过new关键字进行实例化
8.类和接口之间可以通过关键字implements实现接口 实现之后要重写所有的方法
9.接口可以发生向上转型 动态绑定
先继承再实现接口 java中只能继承一个类实现多个接口
接口成员变量默认:public static final
接口当中成员方法默认:public abstract
抽象类和接口都不可以实例化
抽象类A继承抽象类B 抽象类A可以不重写抽象类B中的方法 如果是普通类继承 需要重写
接口间的继承相当于合并
抽象类和接口的区别
核心区别
object类
所有类的父类 尽管无显示继承(默认继承)
equals方法返回true或false
以后自定义的类型 一定重写equals方法
equals方法不能用于基本数据类型的变量(Byte,short,int,long,double,folat,boolean,char)
“==”比较基本数据类型时比较的是表面值内容,而比较两个对象时比较的是两个对象的内存地址值
== 在基本数据类型:值内容, 引用类型时:地址
equals 重写:值内容 , equals不重写:地址
重写hashcode 使得两个对象 逻辑上在同一个位置
自定义类型 比较大小 实现compareable接口
此接口对类的侵入性较强 可扩展性弱
不同包的不同访问
clone方法的异常是受查异常/编译时异常 必须是编译时处理
向下转型 需要强制类型转换
空接口-> 标记接口 证明当前类是可以被克隆的
浅拷贝
并没有对对象中的对象进行克隆
深拷贝
看代码的实现过程
内部类
一个类一个字节码文件
实例内部类
获取实例内部类对象时 依赖外部类对象
1 | OutClass.InnerClass innerClass = new OutClass.new InnerClass(); |
在实例内部内中 定义静态的成员变量需要使用public static final
原因:静态方法首先执行且不依赖任何对象 但内部类的实现依赖外部类
final修饰常量 不需要类加载
类加载的时候不会加载普通成员变量 实例内部类中存在static static是在类加载的时候创建的
当外部类中的数据成员和内部类中的数据成员相同时 可以通过外部类.this访问外部类成员变量(OuterClass.this)
实例内部类对象中包含外部类的this 因此可以通过内部类访问外部类的成员变量(public权限)
注意
1.外部类中的任何成员都可以在实例内部类方法中直接访问
⒉.实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3.在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员来访问
4.实例内部类对象必须在先有外部类对象前提下才能创建
5.实例内部类的非静态方法中包含了一个指向外部类对象的引用
6.外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
静态内部类
获取静态内部类对象
在静态内部类中范根外部类的非静态数据成员
内部类为静态 直接访问即可
1 | OuterClass outclass = new OuterClass(); |
注意
在静态内部类中蓄能访问外部类中的静态成员(要访问 提供外部类对象的引用)
创建静态内部类对象是 不需要先创建外部类对象
匿名内部类
外部类名$内部类名
外部类类名$数字
在匿名内部类中 能够被访问的是没有被修改过的数据
1 | interface A { |
局部内部类
只能定义在方法内部
1.局部内部类只能在所定义的方法本内部使用
2.不能被public、static等修饰符修饰
3.编译器也有自己独立的字节码文件
命名格式:外部类名字$数字内部类名字.class4.几乎不会使用
String类
==比较是否引用同一个对象
任何情况下只要等号两边是引用类型一定注意看此时比较的是什么?如何要比较两个引用所指向对象的内容是否一致
一定重写equals方法–>
不重写就会默认调用object的equals方法
自定义类型一定要重写equels()方法
区别于C语言 java中的字符串没有’\0’结尾
子类重写了父类的方法后进行调用 优先调用子类自己的
1 | String str3 = "abcde"; |
存储双引号引起来的值
存储内容为字符串的常量值
存储步骤:
看 常量池有无当前字符串
无 存入
有 获取已经存储在常量池中的值的地址
StringTable –>其实是一个哈希表
作用
提高存储效率
使用String方法时 默认先进行字符串长度比较 再进行内容比较(hash)return ASCII差值(相同则返回长度)
equals方法比较
比较内容是否相等
返回boolen类型(true/flase)
按照字典序(字符的大小顺序)
先长度后大小
CompareTo方法比较
返回int类型
先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
忽略大小写进行比较
1 | String str3 = "Abcde"; |
字符串查找
从0下标开始
例如lastIndexof()
转化
数值和字符串转化
1 | String s1 = String.value(1234)//整型转字符串 |
大小写转化
1 | String str1 = "hello"; |
java中String是不可变的
在java中 hello->转化不是在原来的字符串基础上转换 而是产生了一个新的对象
字符串转数组
1 | String str1 = "hello"; |
格式化字符串
1 | String s = String.farmat("%d-%d-%d",2023,9,28); |
字符串的替换
1 | String str = "ababcabcdabcde"; |
1 | String ret3 = str.replaceFirst("ab","yub"); |
1 | String ret4 = str.replaceAll("ab","yub"); |
注意
replace和replaceALL的区别
replaceALL支持正则表达式
replace不支持正则表达式
1 | String src = new String("ab43a2c43d"); |
“\”在java中是一个转义字符,所以需要用两个代表一个。例如System.out.println( “\“ ) ;只打印出一个”"。但是“\”也是正则表达式中的转义字符,需要用两个代表一个。
1 | "\\\\"被java转换成"\\","\\"又被正则表达式转换成"\" |
字符串拆分
1 | String str = "name=yub&age=0.4"; |
1 | String str = "yu bo love"; |
注意
1.字符”|”,”*”,”+”都得加上转义字符,前面加上 “\" .
2.是 “" ,那么就得写成 “\\“ .
3.如果一个字符串中有多个分隔符 可以用”|”作为连字符
字符串的截取
1 | String s = "yublove"; |
其他操作方法
1 | String str = " y u b love "; |
字符串为什么是不可变的?
final修饰基本数据类型 基本数据类型的值不能被改变
final修饰引用类型 引用类型的指向不可改变 但内容可改
但value指向的内容可以发生改变
为什么 String 要设计成不可变的?(不可变对象的好处是什么?) 1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
2.不可变对象是线程安全的.
3.不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
不是所有的传引用 都是改变原来的值 具体情况依据代码而定 有可能只改变了指向
StringBuilder
StringBuffuer不能直接赋值
1 | StringBuilder stringbuilder = new StringBuilder("yub"); |
StringBuilder和StingBuffer是可变的 效率更高
StringBuilder不考虑并发
StringBuffer适用于多线程
注意
String和StringBuilder类不能直接转换。
互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法
String、StringBuffer、StringBuilder的区别
1.String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
2.StringBuffer与StringBuilder大部分功能是相似的 StringBuffer采用同步处理,属于线程安全操作;
3.StringBuilder未采用同步处理,属于线程不安全操
异常
编译时异常/受查异常
CloneNotSupported
运行时异常/非受查异常
数组越界
空指针
算数异常
异常的处理
事前防御
事后认错
1 | try{ |
优势:正常流程和错误流程分开处理
异常处理的五个关键字
throw
抛出异常
1 | public static void main(String[] args){ |
注意
1.throw必须写在方法体内部
- 抛出的对象必须是Exception 或者 Exception 的子类对
- 如果抛出的是 RunTimeException 或者RunTimeException 的子类,则可.以不用处理,直接交给JVM来处理
- 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
- 异常一旦抛出,其后的代码就不会执
1 | //throws CloneNotSupportException一般放在方法声明的地方 |
注意
- throws必须跟在方法的参数列表之后
- 声明的异常必须是 Exception 或者 Exception 的子类
- 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可。
关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿 对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
try-catch
1 | System.out.println(10/0); |
这个异常没有被处理 会交给jvm处理 一旦由jvm处理 程序就绪异常终止
1 | try { |
catch一定要捕获一个对应的异常 否则最后还是交给jvm
1 | try { |
Exception是所有类的父类 不能区分exception捕捉的是什么异常 不建议使用exception进行判断
可以通过’|’并写很多个异常检查
或者分开写 将Exception父类写到最后
注意
1.try块内抛出异常位置之后的代码将不会被执行
2.如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序—-异常是按照类型来捕获的
1 | public static void main(String[] args) { |
3.try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获—-即多种异常,多次捕获
- 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)
finally
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能 导致有些语句执行不到,finally就是用来解决这个问题的。
1 | try{ |
注意
finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
finally中的语句一定会执行
throw和throw的区别
throw是抛出一个异常
throws是对一个异常的声明
异常的处理流程
程序先执行try 中的代码
如果try中的代码出现异常,就会结束try 中的代码,看和catch中的异常类型是否匹配.如果找到匹配的异常类型,就会执行catch中的代码
如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者.
无论是否找到匹配的异常类型, finally中的代码都会被执行到(在该方法结束之前执行).如果上层调用者也没有处理的了异常,就继续向上传递.
一直到main方法也没有合适的代码处理异常,就会交给ⅣM-来进行处理,此时程序就会异常终止.
自定义异常
1.自定义异常类,然后继承自Exception 或RunTimeException
- 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
注意
自定义异常通常会继承自 Exception 或者 RuntimeException 继承自 Exception 的异常默认是受查异常 继承自 RuntimeException 的异常默认是非受查异常
受查异常
IOException(同输入输出相关的操作,如无效输入,打开一个不存在在文件)
ClassNotFoundException(使用不存在的类)
特征:编译就不能通过。方法要抛出的受查异常必须在方法头中显示声明,然后编译器会核查是否为所有的受查异常提供了构造器(try-catch)。
非受查异常
ArithmeticException(算数异常)
NullPointerException(指向对象为空异常)
IndexOutOfBoundsException(数组超标异常)
IllegalArgumentException(传递非法参数异常)
特征:可以通过编译,从名字知道,他的错误发生在运行时,上面的 1/0 就是一个算数异常,它可以通过编译,但无法运行。不要求显示声明非受查异常(try-catch是无法解决RE异常的,但是仍旧可以捕捉RE异常)。
数据结构
时间复杂度和空间复杂度
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
两个算法乳沟比较复杂度时 比较最坏情况
递归的复杂度 = 递归的次数 * 每次递归的次数
包装类
java中基本类型不是继承自object 非了在泛型代码中支持基本类型 java给每个基本类型一个包装类型
装箱
把一个基本数据类型转化为包装类型的数据
分类
自动装箱(隐式)
显示装箱
1 | int a = 10; |
拆箱
1 | Integer a = new Integer(10); |
1 | Integer a = 100; |
valueOf底层源码范围为-128~127(256)超出范围相当于new了一个新对象 return false
泛型
一般的类和方法 只能使用具体的类型(基本类型/自定义的类)
JDK1.5引入 泛型 适用于多种类型 从代码上讲就是对类型实现了参数化
引出泛型
1 | class MyArray{ |
1 | class MyArray<T>{ |
虽然数组是Object类型的元素 但是return强转为T类型
1 | class MyArray<T>{ |
泛型的主要目的
指定当前容器 要持有什么类型的对象 让编译器去检查
泛型中不允许 实例化一个类型的数组
1 | public T[] array = new T[10]; |
泛型只接收类 基本数据类型必须使用包装类
<>中只能是引用类型 不能是基本类型
1 | MyArray <int>myArry = new AyArray<>();//x |
泛型是如何编译的
编译的时候 将所有的T擦除为Object 运行时 没有泛型这样的概念【泛型的擦除机制只存在于编译期间】
泛型的上界(extends 拓展)
1 | class MyArray<T extends Number>{ |
1 | class Alg<T extends Compable<T>>{//T一定实现compareable接口 |
泛型方法
1 | class Alg{ |
静态
1 | class Alg{ |
List接口
继承Collection Collection继承于Iterable
线性表 n个具有相同类型元素的有限序列 在该序列上额可以执行增删改查以及变量等操作
List是个接口 不能用来实例化 在集合框架中 ArrayList和LinkedList都实现了List接口
equals 返回值true/false
compareto 比较大于小于等于
数组中的引用类型需要逐个滞空
ArrayList的用法
1 | import java.util.ArrayList; |
1.ArrayLIist是以泛型的方式实现的 使用时必须先实例化
2.ArrayList实现了RandomAccess接口 表明ArrayList支持随机访问
3.ArrayList实现了Cloneable接口 表明ArrayList支持序列化
4.ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
5.和Vector不同 ArrayList不是线程安全的 在单线程下可以使用 在多线程中可以选择Vector或者CopyOnWriteArrayList
6.ArrayList底层是一段连续的空间 可以动态扩容 是一个动态类型的顺序表