目录
3.引用数据类型和基本数据类型(和JavaScript有点像)
一、数据类型
1.基本数据类型
对我而言,比较新奇的是 long a=9999L; 要加L
float b=88.8F; 要加F
2.引用数据类型(除基本类型之外的其他)
二.运算符
1.隐式转换
2. 字符串的+操作 和 字符的+操作
3.扩展赋值运算符
/= += -= *= %=
4.逻辑运算符
| ^ & || &&
^是异或
|| 短路逻辑运算符,当有一个为true不用再检测另一个是否为true,但对于 | 无论怎样都需要检测所以的结果. && 同理.
4.三元运算符
三. 数组
1.数组的静态初始化
2.数组的动态初始化
3.两种初始化之间的区别
4.数组的length属性
利用 length 属性可获取数组的长度
5.产生一个随机数
第一步,导入java.util.Random; 包
第二步,初始化Random 变量名 = new Random();
第三步,赋值给一个变量,可以划定范围
import java.util.Random;
public class RamdomDemo{
public static void main(String[]args){
Random r=new Random();
//number 的范围是一个0~100的整数
int number=r.nextInt(100);
//可通过某些手段来,让范围改变,不包括0
int magic=r.nextInt(99)+1;
//此时范围为1~100
}
}
原来可以直接用origin来划定起始范围,我原来是不知道的。起始数也可以是 负数。
6.利用随机数 打乱一个数组
7.将一个数组的元素 变为 倒序
8.我所出现的错误:
(1)
第一个方括号里面不应该添加内容
四. 方法
1.固定格式
2.方法的重载
方法的重载 只需看参数和方法名 , 跟返回值无关.(跟c++的重载很像)
3.引用数据类型和基本数据类型(和JavaScript有点像)
五. 面向对象基础
1.this关键字
跟JavaScript的作用域链很像.
2.构造方法
3.标准javabean
可用快捷键迅速生成 .
4.面向对象出现的错误
blood 属性已经被 private 修饰了,因此不能通过直接调用来修改它,需要用 方法 来获取或改变它.
5.vscode快捷键
(1)代码格式化shift +alt +f
(2)迅速生成setter/getter方法
如下图, 点击Source Action ,里面有 setter 和getter方法.
6.java里也有类似c语言输出的格式化函数
第一个参数是字符串, 里面可以含有%s,%d ,后面的参数即为%s和%d的内容
7. 键盘录入
两种体系不能混用,不然会出现在c语言里那种,把回车符当作字符录入.
8.对象成员变量默认值的规则
9.对象的内存图
(1)一个对象的内存图
study()方法 执行完毕后出栈,此时main()方法也执行完毕,也要出栈.
于是,main()方法里的 变量 随之消失,
没有变量指着堆内存的那块空间,于是那块空间就会被回收
那块空间被回收了
(2)两个对象的内存图
class字节码文件加载一次后,再用这个类就不需要再加载一次了.
六. 字符串
1.不需要导包
java.lang包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。
2.特点
-
字符串不可变,它们的值在创建后不能被更改
-
虽然 String 的值是不可变的,但是它们可以被共享
-
字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
例子:该图中代码 创造了 两个字符串 ,虽然输出m后结果为 "123",但"12"并没有消失.
3.String 类的 构造方法
(1)public String() 创造空字符串 和 public String(String original) 根据传入的字符串创造字符串很少用,
(2)public String(char[ ] chs) 可以通过 字符数组来改变字符串 的值
(3)byte里面放的是整数,当传入byte[ ] 后,会根据 数字对应的字符 进行创建
4.内存分析
(1)当使用直接赋值的方式时,会用到 StringTable,去寻找 String Table 里面找是否存在这样的字符串.
存在则返回其String Table 对应的地址
不存在则 创建该字符串 并返回其 地址. (节约内存)
5.字符串的比较
6.拼接字符串(有所收获)
(1)原来在求回文数时,要用到 newNum+=singleDigit+sum*10;来让数字倒过来.
int number=123456;
int newNum=0;
while(number!=0){
//求个位数,从右到左
int singleDigit=number%10;
number/=10;
newNum+=singleDigit+sum*10;
}
//循环后,newNum的值 最先求得的个位数为最高位,依次递减.
//即sum为回文数,值为
sout(newNum);
在字符串拼接时,可通过调换字符串的位置来改变,这点真的很神奇.
7.几个常见的字符串方法
8.StringBuilder
(1)提高效率
当进行字符串拼接时,由于拼接一次,便创造一个新的字符串,很浪费空间.
这时,可以用SringBuilder进行创建,拼接时内容是可变的,不会产生新的.
(2)链式编程
(3) StringBuilder的应用场景
字符串的拼接
字符串的反转
(4)StringBuilder常见方法
(5)构造方法
有参构造
StringBuilder sb=new StringBuilder("abc");
无参构造
StringBuilder sb=new StringBUilder();
(6)StringBuilder补充
①StringBuilder除了可以使用 append ()方法 向后追加字符串,
它也可以通过 Insert方法向 前追加字符串
//十进制转化为字符串二进制
int number = 10;
StringBuilder sb= new StringBuilder();
while (number != 0) {
//取余数
int remainder = number % 2;
//求商
number = number / 2;
//拼接在前面,即一直插在为0的位置
binaryStr.insert(0, remainder);
}
String binaryStr=sb.toString();
System.out.println(binaryStr);
9.StringJoiner
1.介绍
2.构造方法
两种构造方法
StringJoiner sj=new StringJoiner(间隔符);
StringJoiner sj=new StringJoiner(间隔符,前缀,后缀);
sj.add(); //往sj里添加一个元素
sj.length(); //返回sj的长度
sj.toString(); //将StringJoiner类型转换为String类型
10.字符串相关底层原理
(1)拼接时无变量参与
(2)拼接时有变量参与,jdk8之前,
变量与字符串相加,拼接一次会创建两个对象,先创建StringBuilder(),再用toString()方法将其转换为字符串. 即 StringBuilder()创建了一个对象,转为字符串又创建一个对象.
jdk8后:如下图,会提前做一个预估,然后把字符串放在一个数组里面,然后变作字符串.
因此,对于 红线 所划的代码,预估s1 ,s2 ,s3的长度,把它们放进数组里面.
但在下图,变量与字符串多次相加,需要预估多次,这其实也是比较浪费时间的.
(3)面试水题
(4)小总结
11.字符串常见方法
12.课后练习的收获
(1)
调用charAt()方法遍历来获取的 子串,也可以用subString()方法来提取字符串
调用String.toCharArray()方法,使 字符串 变为 字符数组,对字符数组里的元素进行改变.
(2)将数字转为字符串(特别简单)
就让 空字符串和数字 相加即可.
七. 集合
1.集合介绍
ArrayList.indexOf(object o); 返回集合中 对象o 的索引值
对于移除元素的方法ArrayList.remove()具有重载函数
不能用基本数据类型,但可以用基本数据的包装类
2.往集合 里 添加内容的 小坑
下图将 学生对象s的地址添加到集合中,所以同一个地址里的内容相同.
补充:
集合里面的不同索引下的内容可以重复;????试了试Chracter的包装类,应该是可以的。
八. 学生管理系统
1.学到以及回顾的知识
(1)跳出循环
变量名: break 变量名;
如下图的loop1
可以从一个地方跳到另一个地方,但要 谨慎的使用.
System.exit(0)
(2)String的startWith()方法
(3)ArrayList 集合
ArrayList <>里面 不可以使用基本数据类型,必须使用引用数据类型.
但是,你可以用 用ArrayList(Character),用Character这个包装类来当作 char来使用.
同理,你可以用其他 基本数据类型的包装类 来实现
泛型的定义主要有以下两种:
不论使用哪个定义,泛型的参数在真正使用泛型时都必须作出指明。
(3)封装思想的应用
2.不同函数 内容重复 后的改善
3.改善性能的一些做法
(1)下图中,需要调用两次 String的length方法,可以进行改善,将 长度 提前 储存在一个变量中
改善后:
4.出现的错误
(1)
我把 lassLetter的判断写错了,lastLetter应该 是 '0'=<lastLetter<='9'
我把其中的 &&错写为||
if(lastLetter=='x'||lastLetter=='X'||lastLetter>'0'||lastLetter<'9'){return true;}
(2)StringBuilder创建的对象后,加 字符 ,会出现奇怪的事情,最后的结果很好笑
探究如下:
九.面向对象进阶
1.static
1.1static 概述
自说自述:当用static 修饰变量和方法时,
① 类名.变量 或 类名.方法() 直接调用,
② 通过 类 创建一个对象, 再通过对象来调用
但对于 一般的成员变量和方法 (无static修饰),只能创建对象后,利用 对象来调用
1.2 工具类
(1) 私有化构造方法, 工具类就是个工具, 不能够创建它的对象,所以要把它的 构造方法 私有化,这样,就不会误操作.
(2)方法定义为静态,即static , 这样可以直接用 类名.方法名() 来调用.
1.3 static注意事项
(1) 静态方法只能访问 静态变量和静态方法.
否则编译器会给你报错
(2)非静态方法可以访问 静态变量,静态方法,
(3)静态方法没有this关键字
对于一般的方法,每创建一个对象,对象拥有属于它自己的成员变量 ,因此有多个对象,this关键字就能确定它是谁,通过调用方法而去修改它.
但对于static定义的静态方法,这是共享的方法,每个创建的对象只共享这一个方法,因此它是独一无二的,没必要取用 this关键字 来区分,只需用类名,方法() 调用就行.
1.4 static原理分析
2.用类创建对象后,成员变量具有默认值
当未对数组初始化时,数组中的元素也会有默认值.
3.类的继承
3.1类的继承概述
3.2继承的特点
(1) 我们来详细分析一下第3条
一个类可以继承一个直接父类,
而一个类的父类也可以继承一个父类,
一个类的父类的父类的父类也可以继承一个父类
................................................................................,难道无穷无尽??????
其实追溯到源头,最牛逼的 是 Object类 ,它是所有类的 父亲,或是父亲的父亲
当我们随便写一个类,而且这个类里面什么也不写,但你却可以调用 Object类的方法
AMAZING!
3.3 子类能继承父类的那些内容
(1)先研究 父类的 构造方法和成员变量 是否能被继承
(2) 父类的 成员方法 能否被继承
①对于父类的成员方法, 只有 不被 private static final 修饰的成员方法才能被继承
②虚方法表可以提高查找 效率
③satic是否也是继承???尚未搞清final好像也能继承,但不能重写??啥情况???
3.4 继承类中访问成员变量和成员方法的访问特点
(1)成员变量的访问特点
(2)成员方法的访问特点
① 继承的类不是有 虚方法表吗,为什么还会有就近原则??????
因为
有 方法重写,虽然有虚方法表,但是当发生 方法重写时,会覆盖 父类的成员方法,因此子类会先找的新的虚方法表中的方法.
②如果想调用父类的方法怎么办?????
可以用 super.父类方法() 来调用父类的方法
基于②的 例子如下图,先利用super关键字调用父类的eat()
3.5 方法的重写
补充:重写时不按照规则时,
例1:父类与子类的方法名一样,但参数列表不一样。
这时就不是重写了,不会覆盖 父类的方法,而是重载,既能调用父类的方法也能调用子类的方法。
分析第5条,只有被添加到虚方法表中的方法才能重写.
我把一个父类的用final修饰的成员方法在子类里进行重写,发现报错了,
因为final修饰的成员方法没有添加到虚方法表中,所以无法进行重写.
3.6继承中的构造方法和this super关键字
①构造方法不会被继承,但可以用super()来执行父类的构造方法
当在 本类的构造方法 调用 本类的其他构造方法时,必须把 调用的构造方法 写在第一行.
此时虚拟机不会在第一行添加super();
这是为啥?
因为 本类的其他构造方法的第一行会默认有个super(),调用该构造方法也就代表:调用了其 第一行默认的super
可以通过 无参构造里面 调用其它 有参构造方法
来在创造对象(new 类名()不 需要加参数)时得到默认值.例如下图
有一个疑问???
为什么通过调用super()的含参构造方法就可以让本类的成员变量被赋值,
因为super()的含参构造里的this关键字所指向 调用它的对象. 像下面就可以构建全参的构造方法.
十.类的多态
1.认识多态
2.多态调用成员的特点
下面这个图很重要, 调用成员方法 编译看左边,运行看右边.
我们知道对于多态时 调用成员方法,编译看左边,运行看右边.
但对于下面这个例子,Husky类 继承于 Dog类
①使用多态,运行时调用子类的方法 , 但此时如果子类没有重写该方法, 就会去调用父类的该方法,
就像下图的例子,如果Husky子类没有eat()方法,就会去找它的父类的eat()方法.
为什么可以找父类的eat()方法?
因为在编译时就会查看父类是否有所要调用的eat()方法,
3.多态的优势和弊端
3.1 优势
像下面的add中的形参为Object ,由于每一个类都直接或间接继承于Object,
因此这就形成了一种多态,Object e= new 子类( );
可以将任何对象加入 ArrayList
3.2多态的弊端
问题: Dog类 继承于 Animal类
Animal a=new Dog();
但我却想调用Dog类的 特有的方法 ,好像是无法实现的???
但通过强制类型转换可以解决这个问题,
要知道父类是比子类大的,因此进行强制类型转换
像下面的Dog d=a;
报错的原因是 a是Animal引用类型 ,d是Dog引用类型,a比d大,不可以把一个大的赋值给一个小的
,除非使用强制类型转换
即为Dog d= (Dog) a;
上面虽然解决了关于多态 调用子类特殊方法的问题,
但 我们很难去 分清楚 所要强制转换的类型 是谁?
即对于 Dog d=(Dog) a 我们Animal类转换为了Dog类型
但我们如果错写为 Cat c=(Cat) a ,就会发生调用错误
原因如下: 因为Animal a=new Dog(); 右边是Dog类,可以用强转将a转换为Dog类,但却不能转换成其它类
为了解决强转时,出现转换错误的情形可以用 instanceof,
在jdk14引入了一个新特性,可以使 instanceof 更加方便
/*
多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
*/
//Animal 是 Dog的父类
Animal a=new Dog();//多态
//
if(a instanceof Dog d); //判断 a是不是 Dog的实例
//相当于
if(a instanceof Dog)
{
Dog d= (Dog)a
} ;
十一. 包和final
1.包
2.final
(1) final 修饰 方法
表示该方法不能在子类中被重写
(2)final 修饰 类
则该类是最终类,不能被继承
(3)final修饰变量
①则该变量是一个常量,它所储存的 数据值 不可改变
②当变量是引用类型时,其存储的是地址值,地址值不可改变, 但是 这个地址值里的内容(成员变量,方法)可以改变
十二.权限修饰符和代码块
1.类的权限修饰符
2.权限修饰符
3.代码块
静态代码块只执行一次!!!
像下图,可以将static ArrayList<User>..........不加static,写进main()方法中,
效果看起来是一样的,但实际上真的一样吗?
但是在 其它类 反复调用main()方法时,此时,main()方法里的 ArrayList<User>.........,以及list.add()会被反复执行.
但你用static 代码块便可以随着类的加载只执行一次 static代码块里面的内容,当多次调用main()方法时,不会让这个初始化过程重复
静态代码块里面只能初始化 static修饰的成员变量。
3.总结
十三. 抽象类和抽象方法(跟继承有关)
1.由于 不能确定父类方法中要写什么,所以只写函数的声明就行,记得加 分号
类需要变作 抽象类
为什么 抽象类 不能创建对象,但还要写 它的构造方法????
因为: 这是为它的子类着想,子类可通过 super() 来 写它的构造方法。
2.抽象方法可以强制让 重写的函数按照一种格式书写
第一个图是父类的eat()方法,第二个图是子类的eat()方法
十四、接口(跟抽象方法有联系)
1.概述
//接口像抽象类一样不能创建对象
2.定义格式
3.接口中成员的特点
4.多学一招,jdk8,9后的新特性
①默认方法
前情回顾:当类实现一个接口时,需要重写接口中的所有抽象方法,
但是当我们不想重写其中的某个方法时应该怎么办?
就可以将接口中的方法 用 default修饰
实操:
(1)定义一个接口,里面有 一个默认方法 一个抽象方法
(2)在 实现类 里可以 不用 重写默认方法(default修饰) ,
但你想重写默认方法时,在实现类中 重写的默认方法 就不要加 default修饰了
②接口中定义静态方法
不加static的私有方法,为默认方法服务。
加static的私有方法,为静态方法服务。
③总结
形参和返回值
十五.匿名内部类(不太熟)
其实叫做匿名内部类的对象 更合适,可以后跟 .方法名() ; 来调用方法
十六.拼图游戏
1.crazy的匿名内部类
很明显,addActionListener需要 一个 ActionListener 类型的参数
而ActionListener是一个接口
因此,我们 要用创建 一个实现ActionListener接口的类 ,并用其 对象作为参数。
如下,当点击 jButton 后,就会执行所传参数mAL中的 actionPerformed()方法,进而实现点击后 所要发生的事。
事情到此感觉较为完美了,但对于按钮,很多都是不一样的,去创建一个实现接口的类,再创建对象进行传参,有些麻烦。
匿名内部类可以解决这个问题,准确来说是 匿名内部类的对象,如果忘记匿名内部类是什么,回去复习!!!
还有一种方法:
import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyFrame extends JFrame implements ActionListener {
// 将Button设为成员变量
JButton jButton1 = new JButton("jButton1");
JButton jButton2 = new JButton("jButton2");
public MyFrame() {
//创建界面 的对象
JFrame jFrame = new JFrame();
//设置界面大小
jFrame.setSize(600, 600);
//使该界面始终在其他应用程序的界面之上
jFrame.setAlwaysOnTop(true);
//将界面放在屏幕中间
jFrame.setLocationRelativeTo(null);
//设置默认关闭模式
jFrame.setDefaultCloseOperation(3);
// 如果不设置Layout那么就显示不出来了
jFrame.setLayout(null);
// jButton.setSize(100, 100);可用setSize设置宽和高
// 设置按钮坐标和宽和高
jButton1.setBounds(100, 100, 100, 100);
jButton2.setBounds(0, 0, 100, 100);
// 给按钮添加ActionListener事件,
//this为本类的对象,由于本类为实现了 ActionListener接口的实现类 ,
//因此可将实现类的对象(this指向对象的位置)作为 jButton1.addActionListener(ActionListener l) 方法的 参数
jButton1.addActionListener(this);
jButton2.addActionListener(this);
jFrame.getContentPane().add(jButton1);
jFrame.getContentPane().add(jButton2);
jFrame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("你好");
Object source = e.getSource();
if (e.getSource() == jButton1) {
jButton1.setSize(200, 200);
System.out.println("jButton1被调用");
} else if (source == jButton2) {
System.out.println("jButton2被调用");
} else {
System.out.println("按钮未被点击");
}
}
}
十七.常用api接口
1.Math类
1.1常用方法
Math.sqrt() 返回平方根 //sqaure root
Math.cbrt() 返回立方根 //cube root
question1:
abs可能会产生 一些与溢出相应的bug,像下图,我明明想要正数,它却给我一个负数,这是因为
int型的变量的 最小值的绝对值 大于 最大值,因此 使用 Math.abs()时 没有对应的正数,所以打印的是一个 负值。
可以用 Math.absExact(), 当 负数无对应正数值后 抛出错误。
具体看下节 1.2Math源码 的内容。
1.2Math源码
①Math类 被final修饰符修饰,表示不能 被继承. 构造方法私有化,不能够创建其相应的对象。
即Math类是一个工具类。
//还记得 final修饰符的用法吗?
//回顾一下,final修饰类,方法,变量 时,发生的奇妙事件。
②解决溢出不报错的问题。
为什么 没有与float double相应的重载方法
好像是 因为 double和float的最大值 远远大于 最小值的绝对值。
当传进来两个参数,代表两个参数的和。(老眼昏花了,这是addExact)
2. System类
2.1常用方法
① System.exit(0) 正常停止
System.exit(非零值) 异常中止
② currentTimeMillis() 返回系统距离 1970年1月 有多少毫秒,可用其计算 程序运行的时间。
下图是一个小用法
③ System.arraycopy(源数组,原数组索引,目标数组,目标索引,拷贝长度 )
注意 引用数据类型 的数组
当元素是 基本数据类型 时
当类型是 引用数据类型 时,在下图中,可以给相同引用数据类型赋值
可以给父类数组赋值,如下图Person类是Student类的父类。
但这方面要联想起 多态 ,什么时候是大转小,什么时候是 小转大,什么时候能调用子类的特有方法。
3.Runtime类
3.1常用方法
代码实践:
3.1.1
Runtime类不能直接创建对象,需要用调用Runtime类的静态方法
看下图源码:Rumtime的方法被私有化,不能创建对象。需要利用 Runtime.getRuntime()
为什么要这样,Runtime表示运行环境,因为运行时虚拟机只能有一个,只能有一个运行环境,因此不能创建多个Runtime类的对象。
怎样改变final定义的常量,system.out中的out好像被改变了
因为out是引用数据类型,所以内容可以变,但指针的指向不能变
4.Object类
4.1构造方法
Object是所有类的祖先,由于它的子类不具有共性,因此它没有成员变量,只有 空参构造方法。
每一个类构造方法的 第一行都有一个默认 的super()来调用父类的构造方法,这也是Object类只有空参构造的所导致的。
4.2常见方法
要想 浅 克隆一个类的对象,
①首先要看 将要克隆的这个类 是否实现了Cloneable 接口,
这个Cloneable接口很有意思,是一个标志性接口,内部没有任何抽象方法,只是作为一个标记。
②由于Object类的 clone()方法由 protected权限修饰符修饰,因此只能在它的子类中成员方法或main方法内使用此方法,但在其他 类 不能够使用此方法。
因此为了 在其它类中 能够使用Object的clone方法,就需要在子类中重写该方法。
如下图,通过 super关键字 调用了父类的clone()方法。
// 父类,也就是Object类的clone方法是 浅克隆
③深克隆应该怎么写
如面的代码,
我们自己再创建一个数组就可以了,字符串会放在 串池中 进行复用,而其他引用类型会创建新的
//Cloneable
//如果一个接口里面没有抽象方法
//表示当前的接口是一个标记性接口
//因此,Cloneable是一个标记性接口,他仅仅起到标记的作用,内部没有任何代码
//现在Cloneable表示一旦实现了,那么当前类的对象就可以被克降
//如果没有实现,当前类的对象就不能克隆
public class User implements Cloneable {
private int id;
private String username;
private String password;
private String path;
private int[] data;
@Override
protected Object clone() throws CloneNotSupportedException {
//先把被克隆对象中的数组获取出来
int[] data = this.data;
//创建新的数组
int[] newData = new int[data.length];
//拷贝数组中的数据
for (int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
//调用父类中的方法克隆对象
User u = (User) super.clone();
//因为父类中的克隆方法是浅克隆,替换克隆出来对象中的数组地址值
u.data = newData;
return u;
}
}
④但在实际开发中,我们往往引入第三方工具
4..3重写父类 Object 的 toString()方法,equals()方法
顺便回顾一下多态
① toString返回值是 包名+类名+地址值,感觉Object类的这个toString方法并没有什么用,
所以可以对toString()进行重写。
②Student类中重写 equals方法,用到了Objects(呲)的equals方法。
下图为Objects 中的静态方法equals(Object a,Object b)
好像,他怎么没有进行全转
我自己加了些注释,如下面代码
@Override
public boolean equals(Object o) {
//判断是不是同一个对象,即地址值是否相同
if (this == o) {
return true;
}
//判断是否为同一个包下的同一种类
if (o == null || getClass() != o.getClass()) {
return false;
}
//由于o是object类型的,接收它的子类时,相当于多态。
//编译看左面,运行看右边.
当父类没有此方法时,会报错。有此方法时,会运行子类的该方法。
//要想调用子类的 特有 成员方法和成员变量,就必须进行强转
//注意!!!当想调用 子类的成员变量 ,也得强转
//Student1 student1 = (Student1) o;
Student1 student1 = (Student1) o;
//判断字符串是否相等时,需要用到字符串的equals方法.
//但在这里,调用了Objects.equals(Object a, Object b)方法
//这个函数调用了字符串的equals方法,但是在调用前 判断了 对象a是否为空
return age == student1.age && Objects.equals(name, student1.name);
}
4.4底层源码
①Object类的toString()方法的源码
②Object类中的equals()方法
它只判断 地址值 是否相同
③String类中重写的 equals()方法
会通过instanceof 判断 ,anObject 是不是String类的对象,如果不是会报错
4.5一个面试题
Stirng类 和 StringBuilder类 会重写 toString()方法。
而且在底层时,会有一系列判断。
5.Objects 类
先下面一个例子,需要 判断 调用equals方法的s1对象 是否为空。有点麻烦,可以用Objects里的equals()方法
isNull() nonNull()方法
6.BigInteger
6.1
数字较大超出long的范围,可以用BigInteger
通过public BigInteger(String val) 构造方法 ,给BigInteger的变量赋值。
可给BigInteger 指定进制, 像下图 将二进制的1000,转为10进制,赋值给 bi3 ,所以bi3值为8
通过 BigInteger的静态方法来获取 BigInteger的对象
BigInteger类型的变量 不能通过 + - * / 来进行直接计算,需要调用相应的方法。
将 BigInteger 类型的变量可以转为int类型,记得用变量来接收。
6.2 BigInteger底层原理
crazy!!!!
十八.正则表达式
1.正则表达式作用一:检验字符串是否满足规则
1.1基本规则
①上面的正则表达式只能匹配单个字符,下面可以用数量词
②下面这个验证邮箱号码的方式,有一点很有趣,把重复 的部分用括号括起来,后面再加 次数
③ (?i) 忽略它后面的字母的大小写,要想忽略某一部分,可以多加一个括号
1.2小结
1.3 出现的错误:
①误写反斜杠 与 正斜杠
②
[\d&&[^0]]\d{0,9} 是正确的
[\d&&^0]\d{0,9} 是错误的,因为^0应该用中括号 括起来
2.正则表达式作用二:在一段文本中,查找满足要求的内容
2.1本地数据爬取
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo6 {
public static void main(String[] args) {
/* 有如下文本,请按照要求爬取数据。
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
要求:找出里面所有的JavaXX
*/
String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//2.获取文本匹配器的对象
//拿着m去读取str,找符合p规则的子串
Matcher m = p.matcher(str);
//3.利用循环获取
while (m.find()) {
String s = m.group();
System.out.println(s);
}
}
private static void method1(String str) {
//Pattern:表示正则表达式
//Matcher: 文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
// 在大串中去找符合匹配规则的子串。
//获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//获取文本匹配器的对象
//m:文本匹配器的对象
//str:大串
//p:规则
//m要在str中找符合p规则的小串
Matcher m = p.matcher(str);
//拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
//如果没有,方法返回false
//如果有,返回true。在底层记录子串的起始索引和结束索引+1
// 0,4
boolean b = m.find();
//方法底层会根据find方法记录的索引进行字符串的截取
// substring(起始索引,结束索引);包头不包尾
// (0,4)但是不包含4索引
// 会把截取的小串进行返回。
String s1 = m.group();
System.out.println(s1);
//第二次在调用find的时候,会继续读取后面的内容
//读取到第二个满足要求的子串,方法会继续返回true
//并把第二个子串的起始索引和结束索引+1,进行记录
b = m.find();
//第二次调用group方法的时候,会根据find方法记录的索引再次截取子串
String s2 = m.group();
System.out.println(s2);
}
}
2.2有条件的爬取数据
①?= ?: ?! 的区别
2.3贪婪爬去与非贪婪爬取
自己实操:
String s = "Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa" +
"经历了很多版木,目前企业中用的最多的是]ava8和]ava11,因为这两个是长期支持版木。" +
"下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
String regex = "ab{1,100}";//数量词后面什么都不加,就是默认的贪婪爬去
String regex2 = "ab{1,100}?";//数量词后面加 ? 非贪婪爬去
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);
System.out.println("数量词后面什么都不加,就是默认的贪婪爬去:" + m.find() + " " + m.group());
//------------------------------------------------------------
Pattern p1 = Pattern.compile(regex2);
Matcher m1 = p1.matcher(regex2);
System.out.println("数量词后面加 ? 非贪婪爬去:" + m1.find() + " " + m1.group());
2.4正则表达式在字符串方法中的使用
①String .replaceAll() ,替换所有
自己实操:
//要求:将海绵宝宝替换成其他动画里的人物
String s = "我的名字是海绵宝宝,我的朋友是派大星,海绵宝宝喜欢抓水母";
String regex = "海绵宝宝";
String newStr = s.replaceAll(regex, "史迪奇");
System.out.println(newStr);
②String.split(regex);
String s = "我的名字是海绵宝宝 , 我的朋友是派大星 , 海绵宝宝喜欢抓水母";
String regex = "海绵宝宝";
String newStr = s.replaceAll(regex, "史迪奇");
System.out.println(newStr);
String regex2 = " *(,|,) *";
//String regex2=" *[,,] *"; 与上面等价
String[] arr = s.split(regex2);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + i);
}
2.5分组捕获
// 补充:非捕获分组 不占用组号
①判断单个字符
括号 括起来的是一个分组,可以用 //组号 的形式,使用某个组中的 内容。
!!!!:使用的是 内容 ,像下面图中 ,
(.)表示一个字符,这是一个组,
当所判断的字符串是 a123a ,(.)中的内容是 a, 使用 \\1 可以获得这个组中的内容 a
②判断多个字符,原理同上。
③要求字符串的开始部分为重复的某字符
注意,组号 是按 左括号的数量算的。
④
当然 你也可以 不写 ?: ,但是 你要知道它是什么。
十九. 时间类
1.Date类
//Date类
Date d1 = new Date();
System.out.println(d1);
//时间原点
Date d2 = new Date(100);
System.out.println(d2);
//Date.getTime() 把日期 转化成对应的毫秒值
long interval = d1.getTime() - d2.getTime();
System.out.println(interval / 1000 / 60 / 60 / 24 / 365);
//设置 距离时间原点 的毫秒值
d2.setTime(0);
System.out.println(d2);
2.Instant类 好像 代替Date类了
3.
4.对于类DateFormatter(); 传进去的参数是 ZonedDateTime类的对象
5.LocalDateTime类 有getMonth()方法 ,getMonthValue() 但没有 getDayOfWeekValue()
6.这两个代表的天数的相差值不一样,duration计算的是 相隔的天数,把年和月都转换为天数。
而Period 只看 今天是几号,看号一样不一样。
// 本地日期时间对象。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 出生的日期时间对象
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
System.out.println(birthDate);
Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + duration);
System.out.println("============================================");
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数
// 当前本地 年月日
LocalDate today = LocalDate.now();
System.out.println(today);
// 生日的 年月日
LocalDate birthDate = LocalDate.of(2000, 1, 1);
System.out.println(birthDate);
Period period = Period.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println("相差天数:" + period.getDays());
System.out.println(period.toTotalMonths());
}
7.DateFormatter类 可以对LocalDate LocalDateTime LocalTime 进行格式化
2.关于时间类的练习题
2.1
isLeapYear这方法真好用。
2.2
二十. 包装类
1.概述
2.Integer类
①Integer的关于int的构造方法和静态方法 过时了,后面有解释,自动装箱和拆箱。
②Integer中关于String的构造方法也过时了,
可用静态方法Integer,vlueOf()或用Integer,parseInt(String) 来讲传进来的字符串转化为Int类型
除此之外,
还有其余的Byte,Short,Long,Float,Double,Character,Boolean包装类,
同样也可以将字符串转换为相应的 byte,short,float,double,boolean基本类型,
但是 字符包装类 没有此方法。
需要注意:
/*
public static string tobinarystring(int i) 得到二进制
public static string tooctalstring(int i) 得到八进制
public static string toHexstring(int i) 得到十六进制
public static int parseInt(string s) 将字符串类型的整数转成int类型的整数
*/
//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
System.out.println(str1);//1100100
//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
System.out.println(str2);//144
//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
System.out.println(str3);//64
//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt("123");
System.out.println(i);
System.out.println(i + 1);//124
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
//细节2:
//8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
String str = "true";
boolean b = Boolean.parseBoolean(str);
System.out.println(b);
3.装箱与拆箱
4.自动装箱和自动拆箱
5.字符串与基本类型之间的转换
5.1基本类型转为字符串类型
5.2字符串类型转化为基本类型
还是 利用的是 包装类的 静态方法 ,将字符串转换为其他基本类型
二十一. 查找
1.二分查找
前提是 数据 应该是 顺序 排放的
下面这段代码有问题,请检查一下!!!!!!!!!
public static int binarySearch(int []arr,int key){
int low=0;
int high=arr.length-1;
while(low<=high){
int mid=(low+high)/2;
if(key>mid){
low=mid+1;
}else if(key<mid){high=mid-1;}
else{
return mid;
}
}
return -1;
}
答案如下:不能让key和mid直接比较,因为mid是一个索引值,并不是数组中的值。
public static int binarySearch(int []arr,int key){
int low=0;
int high=arr.length-1;
while(low<=high){
int mid=(low+high)/2;
System.out.println("mid="+mid);
if(key>arr[mid]){
low=mid+1;
}else if(key<arr[mid]){high=mid-1;}
else{
return mid;
}
}
return -1;
}
2.二分查找的改进(插值查找)
3.斐波那契查找
4.分块查找
除了手动分块,怎样分块是一个很难的事情!
1.定义块
public class Block {
int startIndex;
int endIndex;
int max;
}
2.实现块查找 所要 调用 的方法
public static int blockSearch(int[] arr,Block[] block,int key){
//搜索在块的哪个索引中
int index=getIndex(block,key);
//在该块中顺序查找
for (int i = block[index].startIndex; i <=block[index].endIndex ; i++) {
if (arr[i]==key) {
return i;
}
}
return -1;
}
public static int getIndex(Block[]block,int key){
for (int i = 0; i < block.length; i++) {
if(key<=block[i].max) {
return i;
}
}
return -1;
}
}
二十二. 排序
1.冒泡排序
public static void bubbleSort(int[] arr)
{
//外循环:表示我要执行多少轮。 如果有n个数据,那么执行n - 1 轮
for (int i = 0; i < arr.length-1; i++) {
boolean flag=true;
//内循环:每一轮中我如何比较数据并找到当前的最大值
//-1:为了防止索引越界
//-i:提高效率,每一轮执行的次数应该比上一轮少一次。
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j]>arr[j+1])
{
int t=arr[j];
arr[j]=arr[j+1];
arr[j+1]=t;
flag=false;
}
}
if(flag){break;}
}
2.选择排序
public static void selectSort(int[]arr)
{
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]){
int t= arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
}
3.插入排序
写这个的时候有点坎坷
1.下面是错误的
public static void insertionSort(int []arr){
//1.找到无序的哪一组数组是从哪个索引开始的。
int startIndex=1;
for (int i = 0; i < arr.length; i++) {
if(arr[i]>arr[i+1]){
break;
}
startIndex++;
}
for (int i = startIndex; i < arr.length; i++) {
for (int j = i-1; j>=0; j--) {
//-------------------------------------------------------从下面开始错了
//因为i值只有在内循环结束后才会交换,此时if语句中一直判断的是不变的i位置,交换位置也一直跟这个固定不变的i位置进行交换,不能达到我们想象中的效果。
//如果startIndex大于有序的最后一个元素,不用对startIndex进行交换
if(arr[i]>=arr[j]){
break;
}
//交换位置
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
2.下面是正确的
public static void insertionSort(int []arr){
//1.找到无序的哪一组数组是从哪个索引开始的。
int startIndex=1;
for (int i = 0; i < arr.length; i++) {
if(arr[i]>arr[i+1]){
break;
}
startIndex++;
}
for (int i = startIndex; i < arr.length; i++) {
for (int j = i-1; j>=0; j--) {
//如果startIndex大于有序的最后一个元素,不用对startIndex进行交换
//思考一下这里为什么不用 arr[i]>arr[j] ???
if(arr[j+1]>=arr[j]){
break;
}
//交换位置
int t=arr[j];
arr[j]=arr[j+1];
arr[j+1]=t;
}
}
}
3.插入排序还可以进行改善
public static void insertionSortImproved(int []arr){
//1.找到无序的哪一组数组是从哪个索引开始的。
int startIndex=1;
for (int i = 0; i < arr.length; i++) {
if(arr[i]>arr[i+1]){
break;
}
startIndex++;
}
for (int i = startIndex; i < arr.length; i++) {
int j=i;
int temp=arr[j];
while(j>0&&temp<arr[j-1])
{
arr[j]=arr[j-1];
j--;
}
arr[j]=temp;
}
}
4.每次写插入排序都有新感受
//1.找到索引位置顺序
int startIndex=1;
for (int i = 0; i < arr.length; i++) {
if(arr[i]>arr[i+1]){
break;
}
startIndex++;
}
//2.将索引处的元素插入到前面的有序数组中
for (int i = startIndex; i <arr.length ; i++) {
//3. 如果要插入的数比它前面的第一个数大,直接跳出循环。其实也可以不写下面这行,因为运行它下面的代码不会对结果造成影响
if(arr[i]>arr[i-1]){break;}
//4.去给要插入的数找寻插入位置
// 注意:while循环中进行比较时,应用 要插入的值 去跟后面比较,不能用arr[j]<arr[j-1],
int j=i;int insertValue=arr[i];
//j-1>=0 来保证索引不越界,也可以写j>0,但我推荐用j-1,因为在希尔排序中 需用j-gap来判断索引是否越界
while(j-1>=0&&insertValue<arr[j-1]){
arr[j]=arr[j-1];
j--;
}
arr[j]=insertValue;
}
}
4.快速排序(不熟悉)
1.先让end去移动,找到一个比基准数小的元素,然后才能去移动start来找到一个比基准数大的元素进行交换。
public static void quickSort(int []arr,int i,int j){
//我的思路:把比基准数大的元素放在右边,反之放在左边。
if(i>=j){return ;}
//第一次先让 第一个元素作为基准数
int start=i;
int end=j;
//pivotKey是基准数,注意pivotKey是数组中的元素,并不是索引
int pivotKey=arr[i];
//先向左移动 end 找比基准数小的数
//因为一趟可能会 交换好几次start和end ,外循环要定义一个start!=end
while(start!=end){
//在这里arr[end]可以 大于或大于等于 pivotKey
while (arr[end] > pivotKey&&end>start ) {
end--;
}
//移动start 找比基准数大的数
//在这里,arr[start]必须小于等于pivotKey
//举个例子,当arr[]={6,6,6,2,3}
while (arr[start] <= pivotKey && start < end) {
start++;
}
//当start和end到达一个位置后,令 基准数与arr[high]或arr[start]交换位置
swap(arr,start,end);
}
//最后还需要将基准数与start或end交换位置
swap(arr,i,start);
quickSort(arr,i,start-1);
quickSort(arr,start+1,j);
}
public static void swap(int[]arr,int index,int index2){
int t=arr[index];
arr[index]=arr[index2];
arr[index2]=t;
}
5.希尔排序(直接插入排序的改进)
5.1算法介绍
5.2算法实现
每次进行分组,然后在各个组内进行直接插入排序 。
继续分组,直至组数为1后,进行最后一次排序。
public static void shellSort(int []arr){
int length=arr.length;
//外循环进行 进行分组
for(int gap=length/2;gap>=1;gap/=2){
//需要找到要插入组中的元素
/*1.我们可以 以每一组的第一个元素,作为一个有序序列,然后将每组中后面的元素插入到前面的有序序列中
* 2.但每组后面的元素应该怎么确定呢?就利用gap这个间隔,gap为第一组的第二个元素,gap+1是第二组的第二个
* 例{1,2,3,4,5,6,7,8,9}
* gap=9/2=4
* {1,5,9}一组 {2,6}一组
* {3,7}一组,{4,8}一组
* */
for(int i=gap;i<length;i++)
{
//记录被插入数
int insertValue=arr[i];
//由于在找寻插入位置时,i会发生改变,所以要用j去代替它的作用
int j=i;
while(j-gap>=0&&insertValue<arr[j-gap])
{
//进行移位
arr[j]=arr[j-gap];
//j-=gap;意味着 插入点 变为 从当前位置变为 本组中前一个位置。
j-=gap;
}
//把插入值填进去
arr[j]=insertValue;
}
}
}
二十三. 算法API
1.Arrays.toString()源码
参数不止是 short类型的数组,还有其他的重载函数
public static String toString(short[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
2.Arrays.binarySearch()
注意:使用这个方法的前提是 数组是有序的,不然返回的索引会有很大的问题。
我再解释一下为什么 -插入点 还要再减去1 ?不减1不行吗?
引入一个场景,
①要查询的值 恰好在 0索引,这时会返回0
②在数组中并未查到此值,于是去返回插入点,插入点恰好在0索引 ,此时 -0=0
无法区分返回的索引0 是数组中存在的值,还是此值的插入点。
综上,当数组中不存在 要查询的值时,返回 负的插入点的索引-1
3.Arrays.copyOf(老数组,新数组的长度) 该方法返回新数组的地址,需用数组来接收。
4.Arrays.copyOfRange(老数组,int from,int to);
注意点跟Arrays.copyOf()相同
5. Arrays.fill(数组,值)
6.Arrays.Sort() !!!重点
6.1默认情况
6.2按指定的规则排序 (只适用于引用类型)
注意重写的方法中 不要直接返回0,如果直接返回0就缺乏了o1与o2之间的比较,需要
return o1-o2; 逆序
return o2-o1; 正序
二十四.lambda表达式
1.简单介绍(@FunctionInterface注解很重要)
@FunctionInterface注解可以检验错误,即检测是否是 接口并且接口中只有一个抽象方法
2.省略写法
3.小练习
3.1匿名内部类写法
3.2lambda表达式写法
二十五.五道算法题
1.跟排序有关
备用知识:String类的 compareTo()方法
步骤如下:
2.剩余跟递归有关,就不写了
二十六. 集合进阶
1.集合体系结构
下图中 红色框的是接口 ,蓝色框的是实现类
2.单列集合Collection
2.1 boolean add()方法
2.2 void clear()方法 boolean remove(E e)方法
2.4 boolean contains() 很重要,对于自定义对象,要重写equals()方法
2.5 判断集合长度是否为零
3.遍历方式
背景:由于 set接口下的实现类 无索引,不可重复,无序
因此 ,一般的for循环只能给 list接口下的实现类使用。
为解决此问题,我们要引入其他的遍历方式。
3.1迭代器
遍历时不能用 集合的移除方法去移除元素,只能用 迭代器的移除方法移除元素。
细节注意点:迭代器遍历时不能用集合的方法进行删除或增加
代码演示:
3.2增强for遍历
3.3 lambda
4.List集合常用的方法
注意 Collection的方法也被List继承了,想remove(Object o) 移除对象,remove( int index)移除索引。
但当用Integer包装类后,事情就变得有趣起来,你怎样区分 remove(1) 这个1是int类型还是Integer引用类型。
4.2 List系列集合的两个删除的方法
Integer i=1; 或用 Integer i=Integer.valueOf(1)
直接用第一种写法就行,第二种没什么用
4.3list的遍历方式
4.4列表迭代器(特有)注意写法
previous()有局限性,因为索引一般从 0 开始 ,这时无法指针无法前移来获得元素。
自己实践:值得注意迭代器的删除方式 和 ArrayList的删除方式不同。
ArrayList<Integer> arrList = new ArrayList<Integer>();
for (int i = 0; i < 5; i++) {
arrList.add(i);
}
Integer i = 1;
//ArrayList的移除方式,并且它有两个移除方式,里面要有参数,而且两个方式不一样
//移除 Integer类型的元素,注意不是索引
arrList.remove(i);
Integer integer;
//记得写<Integer>
ListIterator<Integer> lit = arrList.listIterator();
while (lit.hasNext()) {
integer = lit.next();
if (integer == 2) {
//ListIterator<E>的移除方式 remove() ,里面不需要参数
lit.remove();
}
}
System.out.println(arrList);
}
4.5总结
5.ArrayList源码(抽空实现)
6.LinkList源码 和 迭代器源码
二十七.泛型
1. 回首过去,没有泛型。
2.泛型的好处
但它是假泛型,在编译期间就 把 异常避免,但在字节码文件中 AraryList会发生泛型擦除。
3.泛型类
3.泛型方法
4.泛型接口
4.1实现类给出具体类型。
4.2实现类继续延续泛型。
在创建实现的对象时,再规定类型。像ArrayList一样,记得在括号内写上 引用类型
5.泛型不具备继承性
这个数据可以理解为 放在列表中 的 引用数据类型,众所周知,类具有继承性。
5.1何为数据,何为泛型
下面我给出一个method方法,形参为 泛型
//当形参类型为是一个泛型,泛型里面写的是Grandpa,那么传进去的实参括号中的类型也必须是Grandpa,即使Son和Father是子类也不行。
//但当形参不是泛型,而是像Grandpa g 这样的形参,那么就可以将 Son 类和Father类的对象传进去。对这就是多态。
完整代码如下:
ArrayList<Grandpa> list = new ArrayList<>();
ArrayList<Dad> list2 = new ArrayList<>();
ArrayList<Son> list3 = new ArrayList<>();
//当形参类型为是一个泛型,泛型里面写的是Grandpa,那么传进去的实参括号中的类型也必须是Grandpa,即使Son和Father是子类也不行。
method(list);
method(list2);
method(list3);
//但当形参不是泛型,而是像Grandpa g 这样的形参,那么就可以将 Son 类和Father类的对象传进去。对这就是多态。
method2(new Grandpa());
method2(new Son());
method2(new Dad());
}
public static void method(ArrayList<Grandpa> list) {
}
public static void method2(Grandpa g) {
}
}
class Grandpa {
}
class Dad extends Grandpa {
}
class Son extends Dad {
}
6.泛型方法的弊端和使用 通配符 解决问题
注意写法
7.总结
8.平衡二叉树(很不熟悉)
8.1左旋与右旋
左旋后
右旋
9.平衡二叉树需要旋转的四种情况
9.1左左 (一次左旋)
9.2左右(先左旋再右旋)
9.3右右(一次左旋)
9.4右左(先局部右旋,再左旋)
先局部右旋
再整体左旋
末尾:发现自己对protected 权限修饰符不太了解
①创建一个包 protect ,里面一个 Animal类,一个Dog类,一个Test测试类,Dog类继承于Animal类
二十八.Set集合系列
1.简单介绍
2.添加元素的返回值可能为false
3.小结
4.HashSet
4.2
因为默认是根据 地址 计算 哈希值,因此要重写hashCode方法,去比较根据对象的属性生成hash值。而且重写equals方法很有意义,能够比较属性值是否相等。
4.3三个为什么
问题1:
当存元素时,会根据hash值来存,下面是要存的索引位置
所以这个索引位置 是 无规律性的,当进行遍历时,从0索引开始遍历,看0索引元素下有没有悬挂元素,若悬挂则进行遍历,然后遍历1索引,依次遍历
问题2:
不是有个数组吗?怎么不能用索引?
因为一个数组的一个索引 下挂有 多个元素 ,你没法通过一个 索引 保证获得 特定的元素
问题3:
数据去重是个很有趣的东西,对于自定义的对象,我们通过重写hashCode方法来 根据属性值计算hash值,然后进行 下面的计算 来存在此索引
①若该索引中的元素值为null,则将 要存的元素存进去
②若非空,则将要存的 元素 依次与该索引下的所有元素去 用equals()方法比较属性值是否相等,IF相等,则不用存; IF不相等,则将 要存的元素 挂在最下面。
5.LinkedHashSet
6.TreeSet
6.1简单介绍
6.2第一种排序方式,类实现Comparable接口,并重写里面的方法
①在这里可以回顾一下,当接口是泛型接口时,可以怎样 创造实现这个接口的类。
一个方法是 不延续泛型,在implements interface<类型>,指定类型是什么
一个是像下图一样,使用泛型类,延续泛型
注意this.o是要添加的元素
6.3第二种排序方式(通过构造方法指定排序)
6.4它跟Arrays,sort(数组,指定排序方式) 很像
但不用实现接口
6.5细品lambda表达式
所有符合条件的匿名内部类都可以 写成 (参数)-> { } 的形式,不需要关心它在哪个方法的形参里,我原来理解错了,以为forEach和lambda表达式是相互关联的所以 可以写出 (参数)-> { } 的形式,但其实是因为 实参为 匿名内部类的原因 ,只要是符合条件的匿名内部类,都可以这样写。
6.6两种排序方式 何时用
对于我们自定义的对象,我们好像用这两个指定排序方式都可以,因为我们可以重写compareTo()方法 或去 在创建集合时自定义Comparator比较器对象。
但对于String类,Integer类等类 ,java已经事先将这些类的compareTo()方法重写了,因此会按照java的规则去比较。
当我们想打破枷锁去让String类换种方式去比较,就可以利用Comparator比较器。
下面去看一段代码:但自己写的Comparator 运行的结果有些不尽人意
TreeSet<String> ts = new TreeSet<>();
ts.add("aa");
ts.add("ba");
ts.add("c");
ts.add("ab");
for (String t : ts) {
System.out.print(t + " ");
}
//当我们想比较字符串的长度
System.out.println();
TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
ts2.add("aa");
ts2.add("ba");
ts2.add("c");
ts2.add("ab");
for (String s : ts2) {
System.out.print(s + " ");
}
运行结果如下:怎么回事,我自己写的Comparator 最后 运行的 时候,我向TreeSet ts2 添加了4个元素,结果却只出现两个。
这是因为 TreeSet去重的结果,当返回0,代表比较的结果相等,就不会把元素添加进去。
AMAZING!!!!
6.7小总结
②创建一个包 protect2,里面一个Cat类,一个Test测试类,Cat继承于Animal类
③在 包protect 里面调用 Dog类创建的对象的 eat()方法。
父类的eat()方法 被 protected修饰,子类继承后该方法后,可以在本包中任何类中 或其他包的子类 ,使用该方法。 但如果在 另一个类里创建对象,则会报错
如下图,Dog类与Animal类位于一个包中, 所以在protect包中 Test类中创建Dog的对象并调用protected修饰的方法 不会报错
但在 protect2包中,由于Cat是Animal的子类,因此在Cat类中创建Cat类的对象并 使用父类的protected修饰的方法,不会报错。
但对于protect2包中的Test类,它并不是Animal的子类,因此在创建Cat类的对象后,并不能调用eat()方法。会报错!