Java基础
2022-03-24 20:27:28 0 举报
AI智能生成
登录查看完整内容
史上最全Java基础+进阶知识梳理
作者其他创作
大纲/内容
Java是一门编程语言,它是由詹姆斯-高斯林1995年创造的
什么是Java
开源
面向对象
Java的跨平台是依赖于JVM跨平台
跨平台
Java的特点
开发工具
jvm(运行环境)
核心类库
jdk(开发环境)
JDK
Java开发环境
环境配置
Java概述
单行注释 //
多行注释 /**/
文档注释 /***/
注释
有特殊含义的单词都是关键字
关键字
1、必须是数字、字母、下划线、$符
2、不能是数字开头
3、不能是关键字
1、见名知意
小驼峰(变量、方法名)
大驼峰(类名、接口名)
峡谷先锋(变量名)
4个命名习惯
标识符(所有的名字都是标识符) 注意事项:
整形
浮点型
布尔型
字符型
字符串
null
常量
byte 1字节
short 2字节
int 4字节
long 8字节
flaot 4字节
double 8字节
char 2字节
Boolean(布尔类型不参与运算)
基本类型(四类八种)
类
接口
int[] arr = new int[4];
三种定义格式
数组的访问方式是通过索引进行访问的
数组的索引是从0开始的
数值的最大值位数组长度-1,超过范围将会提示数组越界
数组的访问
1、一个数组的内存图
2、两个数组的内存图
3、两个数组指向同一个数组的内存图
数组的内存图
1、空指针异常:使用null对其进行了赋值
2、索引越界异常:访问的数组索引是数组中没有的
数组常见的问题
数组的遍历方式
一维数组
多维数组
数组
引用类型
1、使用之前必须赋值
2、变量可以连续定义
3、同一范围内不能连续定义
4、变量只在当前范围内有效
5、F/L
变量的注意事项
比int大的自动转换为大的类型
比int小的自动转换为int类型再进行运算
1、隐式转换
明确能转过去的
明确传不过去的
强行取出小数点的
2、强制转换
比int小的类型才有常量优化机制(注:String类型也有!)
小数类型也有数据类型(double)
long类型的数据后面为什么加L?
常量优化机制
boolean类型不参与
AscII码表
char和整形的转换
类型转换注意事项:
类型转换
变量
数据
基本符号
+
-
*
/
%
基本算术运算符
++
--
1、不论自增或自减放在变量的前面还是后面,当程序经过后都会进行运算
2、自增或自减放在变量的后面,那么i++这个表达式整体取得是没有经过运算的值
3、自增或自减放在变量的前面,那么++i这个表达式取得的是i+1之后的值
注意事项:
自增自减运算符
算术运算符
基本赋值(=)
+=
-=
*=
/=
复合赋值
赋值运算符
比较运算符
普通逻辑
短路逻辑
逻辑运算符
三元运算符
位运算
运算符号
符号
代码的构成
顺序结构
if(){ }
if(){ }else{ }
if(){ }else if(){ }else if(){ }else{ }
if的三种格式
1、case后面必须是常量
2、case后面不能重复判断
3、switch小括号中的数据类型必须是:byte、short、char、int、String、枚举
4、switch中的default可以省略
5、switch中的default可以放在任意位置
6、break用来结束switch的,break可以省略,但是会发生case穿透问题
switch注意事项
switch
1、能用switch的地方那么就能用if表示
2、switch的效率必if高, 开发中的规律:代码越麻烦,执行效率越低
switch和if的比较
选择结构
for循环
while循环
dowhile循环
1、while循环和while循环在技术上没有任何的区别
2、习惯上for循环用在有明确范围的地方,while循环使用在没有明确范围的地方上 如珠穆朗玛峰问题!
while循环和for循环的比较:
循环结构
结构语句
方法的作用:方法可以提高全局代码的复用性
public static void 方法名(){\t 语句;}
没有参数,没有返回值的方法
带参数的方法
带返回值的方法
定义格式
1、循环也可以提高代码的复用性,但它值提高的局部代码的复用性,而方法提高了全局代码的复用性
2、方法定义之后,不调用是不会执行的,main方法才是程序的入口,只有在main方法中调用了程序才会执行
3、方法都是独立的,平级关系,不能嵌套定义
4、方法的定义并没有先后顺序
5、main方法调用另一个方法后,只有等待另一个方法执行完成后才会继续执行
方法定义的注意事项
方法的使用
1、方法不能嵌套定义
2、方法与方法之间没有先后顺序
3、方法不调用不会执行,因为main方法才是程序的入口
1、用来终止该方法
2、用来将接收到的值返回个调用者
return的作用
4、void的防范说明方法内是不能有返回值的,但是可以写一个单独的return;来终止方法
5、如果该方法有返回值的类型,那么这个方法则必须各个逻辑上都应该有返回值
方法注意事项
在同一个类中出现了相同方法明的方法,则可以使用重载进行定义对其进行优化
什么是重载
方便开发人员的记忆,减少负担
重载的作用
方法名相同
参数列表不同
与返回值类型无关
重载的特点
方法 的重载
方法
面向过程——步骤化:面向过程是具体化、实例化的,解决问题需要一步步的分析与实现
面向对象——行为化:面向对象是模型化的,当需要什么功能直接进行调用就行,不必一步步的实现
优点:性能比面向对象高,因为类调用时需要实例化,开销较大,比较消耗资源
缺点:不易复用,不易维护,不易扩展
面向过程
优点:易维护,易复用,易扩展,由于面向对象的三大特性,所以易设计出低耦合的系统,使系统更加灵活,更易于维护
缺点:性能比面向过程差
区别:
面向对象与面向过程
类是对事务的描述、模板,是抽象的
对象是对事物的具体表现,是具体存在的
对象
类与对象
一个对象的内存图
两个对象的内存图
两个引用指向同一个对象的内存图
内存图
定义位置:方法内部
内存位置:栈内存,跟着方法走,方法在栈内存中执行
生命周期:随着方法的调用进栈而存在,随着方法的消失而消失
初始化:没有默认值,所以在使用之前必须先赋值
局部变量
定义位置:类中,方法外
内存位置:堆内存中,因为它在对象里面,对象在堆内存中
当栈内存中,没有任何的变量指向这个对象,那么这个对象会被Java的底层的垃圾回收器认定为垃圾,定期轮巡执行,进行垃圾回收
对象的消失:
生命周期:随着对象的创建而创建,随着对象的消失而消失
初始化:有默认值,所以在使用之前可以不用进行赋值
成员变量
成员变量和局部变量的区别
面向对象基础
一个模块对于扩展是开放的,但是对于修改是关闭的、不允许的
开放闭封原则(OCP)
使类的功能更加的单一
单一责任原则(SRP)
子类可以替换父类出现在父类出现的任何地方
里氏替换原则(LSP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。
接口隔离原则(ISP)
层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
依赖倒置原则(DIP)
一个对象应该对其他对象保持最少的了解.
迪米特原则(最少原则)
优先使用对象组合,而不是继承来达到复用的目的。一般而言,如果两个类之间是"Has-A"关系应使用组合或聚合,如果是"Is-A"关系可使用继承。
合成复用原则
程序设计七大原则:
1、private提高安全性
2、方法提高了代码的复用性
3、封装数据 JavaBean类 ——作用用来封装数据的
封装的好处
基本数据类型
引用数据类型
数据类型
注:当不写构造方法时,系统将会自动提供一个默认的无参构造方法,当自己写了一个构造方法后,系统将不会提供无参构造方法
作用:用来创建对象,所必须执行的方法,构造方法包含有参构造和无参构造。
1、方法名字必须和类名一样
2、构造方法没有返回值类型 void也不能有
3、构造方法必须 只能用new来调用
4、不能写返回值 (但是可以单独写一个return,用地是为了强制中制构造方法)
定义格式:
构造方法
调用:构造方法的作用是创建对象,只能使用new进行调用,不能使用对象去调用
如果一个类中 没有手动写出构造方法,那么系统 会提供给你一个 构造方法。手动写出构造方法之后系统就不再提供了
1、构造方法 顾名思义 "构造" 也就是说 这个方法是为了创建对象而存在的,换句话说, 如果一个类中没有构造方法 那么这个类创建不了对象。
2、构造方法可以进行重载
3、构造方法作用, 在创建对象的时候 顺便给 属性赋值
封装
使用:子类使用extends关键字完成继承
注意事项: 父类私有的内容 子类不能直接使用,Java只支持单继承,不支持多继承,但允许多层继承
好处:提高了代码的复用性,维护性,继承是多态的前提
弊端:提高了代码的耦合性(耦合就是联系的意思)
继承的好处和弊端:
\t\t\t\tclass Computer {\t\t\t\t\tpublic void playGame(){\t\t\t\t\t\tSystem.out.println("电脑运行游戏");\t\t\t\t\t}\t\t\t\t}\t\t\t\tclass Student {\t\t\t\t\t\t\t\t\t\tpublic Computer dianNao = new Computer();\t\t\t\t}\t\t\t\tclass Demo {\t\t\t\t\tpublic static void main(String[] args){\t\t\t\t\t\tStudent stu = new Student();\t\t\t\t\t\tstu.dianNao.playGame(); \t\t\t\t\t}\t\t\t\t}
开发中的原则:高内聚,低耦合
1、在Java中只支持单继承,不支持多继承
2、Java中类支持多层继承
3、父类不能调用子类
继承的特点:
首先在子类局部范围找
在子类成员范围找
最后在父类成员范围找
this代表本类对象的引用
super代表父类对象引用
this和super
注:如果子父类中出现了重名的成员变量,通过就近原则,优先使用子类的,如果使用父类的可以通过supper进行区分
成员变量:在子类方法中访问一个变量
案例1:\tpublic class Person { \t\tpublic void show(){\t\t\tSystem.out.println("helloworld");\t\t}\t}\tpublic class Student extends Person {\t\t}\tpublic class Demo {\t\tpublic static void main(String[] args){\t\t\tStudent stu = new Student();\t\t\tstu.show(); \t\t}\t}
子类成员范围找
案例2:\tpublic class Person { \t\tpublic void show(){\t\t\tSystem.out.println("fu");\t\t}\t}\tpublic class Student extends Person {\t\tpublic void show(){\t\t\tSystem.out.println("zi");\t\t}\t\t\t\t//super.show(); // 编译报错 所有的代码(除了定义成员变量的语句)全部都要写在方法里面 因为你不写在方法里面 根本就没法执行。\t\t\t\tpublic void method(){\t\t\tsuper.show();\t\t}\t}\tpublic class Demo {\t\tpublic static void main(String[] args){\t\t\t\t\t\tStudent stu = new Student();\t\t\tstu.show(); //zi\t\t\t\t\t\t//super.show(); \t\t\t\t\t\t/*\t\t\t错的原因\t\t\t1: super 放在哪个类中,就代表的是哪个类的父类。\t\t\t2: main是static的 静态的里面 不能有this 更何谈super呢\t\t\t*/\t\t\tstu.method(); // fu\t\t\tPerson p = new Person();\t\t\tp.show(); // fu\t\t\t\t\t}\t}
父类成员范围找
注:在继承体系中,子类出现和父类一摸一样的方法声明,这就是方法重写。
//A包\tpublic class Fu {\t\tvoid show(){\t\t\tSystem.out.println("fu");\t\t}\t}//B包\timport A包.Fu;\tpublic class Zi extends Fu {\t\tpublic void show(){ // 正确 \t\t\tSystem.out.println("zi"); \t\t}\t}//B包\timport A包.Fu;\tpublic class Zz extends Fu {\t\t@Override // 编译报错 这个@Override的意思是, 这个@Override下面的那个方法 是重写的父类的方法。 然而私有的不能被重写 所以 报错。\t\tpublic void show(){ // \t\t\tSystem.out.println("zz"); \t\t}\t}
私有的(子类看不到的)方法不能重写
class Fu {\tpublic static void show(){\t\tSystem.out.println("fu");\t}}class Zi extends Fu {\tpublic static void show(){ //正确 \t\tSystem.out.println("zi");\t}}class Zz extends Fu {\t@Override //编译报错 因为你加了之后, 这个@Override的意思是, 这个@Override下面的那个方法 是重写的父类的方法。 然而私有的不能被重写 所以 报错。\tpublic static void show(){ \t\tSystem.out.println("zi");\t}}
static的方法不能重写。 当子类出现了和父类相同的方法声明的静态方法,可以理解为子类的静态方法只是覆盖了父类的静态方法。
class Fu {\tpublic void show(){\t\tSystem.out.println("aaa");\t}}//同一个包下class Zi extends Fu {\tvoid show(){ // 编译报错 因为我看的到 父类的show方法,子类写show 就是自动在重写, 重写权限修饰符却小于父类 所以报错 \t\tSystem.out.println("aaaaa");\t}}
子类重写父类的方法 权限必须大于等于父类的权限
重写
成员方法:
1、子类的所有构造方法的第一行 都有一个默认的super() 用于访问父类的无参构造
2、当子类自己写出 super() 、super(参数) 、this() 等 系统就不在会默认提供super()
3、访问构造方法的语句 必须放在 构造方法的第一行
4、当父类没有无参构造方法的时候 子类初始化之前 先初始化父类。
5、子类初始化之前,必须先初始化父类 初始化一次(有且只有一次) 而this和super都放在构造方法的第一行,所以他俩不能共存
注:子类初始化之前必须先初始化父类,而且必须是初始化一次。
子类在创建对象的时候,必须去调用父类的构造方法(语法规定),为什么语法这个规定呢? \t是因为:子类创建对象要使用的时候,保不准就会用到父类的内容, 所以 一定是先去把父类在内存中初始化,那么如何把父类在内存中初始化么,\t就是访问父类的构造方法,这就是在内存中初始化。 父类初始化完了之后,子类再继续初始化子类的。\t因此构造方法的访问特点如下:
继承中的成员特点:
继承
1、必须要有 继承或实现关系
class Animal {}class Cat extends Animal{}Cat c = new Cat(); //猫是一只猫Animal a = new Cat(); // 猫是一只动物 // 多态
interface Jumping{}class Dog implements Jumping{}Jumping j = new Dog(); // 多态
2、必须要有 父类的变量(引用) 指向子类对象
之所以很多的教程中说必须有方法的重写,是因为 我们去应用多态的时候99%以上的情况 全部都是用方法的重写, 如果你没有方法的重写,那么你使用多态就没有任何意义,所以 很多的教程中 必须有方法的重写。
3、有方法的重写(非必须)
多态的前提
class Person {\tint age = 80;\tint a = 20;}class Student extends Person {\tint age = 25;\tint b = 30;}class Demo {\tpublic static void main(String[] args){\t\tPerson p = new Student();\t\tSystem.out.println(p.age); // 80 \t\tSystem.out.println(p.a); // 20\t\t//System.out.println(p.b); // 编译报错\t}}
结论:多态中调用成员变量 全部都是调用父类的 父类没有就编译报错。
成员变量:
class Person {\tpublic void show(){\t\tSystem.out.println("Person");\t}\tpublic void method(){\t\tSystem.out.println("method");\t}}class Student extends Person {\tpublic void show(){\t\tSystem.out.println("Student");\t}\tpublic void function(){\t\tSystem.out.println("function");\t}}class Demo {\tpublic static void main(String[] args){\t\tPerson p = new Student();\t\tp.show(); // Student\t\tp.method(); // method\t\t//p.function(); //编译报错, 因为父类没有\t}}
结论: 多态中调用成员方法 除了重写的方法 是调用子类的 其他的方法都是调用父类的 父类没有就报错。
总体结论: 多态中除了重写的方法是调用的子类的 其他所有所有的东西都是调用父类的 如果父类没有就报错。
多态中成员访问特点:
设计程序原则:高内聚、低耦合
模拟一个场景: 饲养员 饲养 动物园里面的动物。abstract class Animal{\t \tpublic abstract void eat();}class Cat extends Animal{\t\tpublic void eat() {\t\tSystem.out.println("猫吃鱼");\t}}class Dog extends Animal{\tpublic void eat() {\t\tSystem.out.println("狗吃骨头");\t}}class Tiger extends Animal{\tpublic void eat() {\t\tSystem.out.println("老虎吃羊肉");\t}}class Feeder{\tpublic void feed(Animal a){ // Animal a = new Tiger();\t\ta.eat(); \t}\t/* public void feed(Cat a) {\t\t\t\t\t\ta.eat(); } public void feed(Dog a) {\t\t\t\t\t\ta.eat(); } public void feed(Tiger a) {\t\t\t\t\t\ta.eat(); } */}public class Demo {\tpublic static void main(String[] args) {\t\tFeeder f = new Feeder();\t\tCat c = new Cat();\t\tf.feed(c);\t\tDog d = new Dog();\t\tf.feed(d);\t\tTiger t = new Tiger();\t\tf.feed(t);\t}}
开发写代码的原则:尽量提高代码的复用性和扩展性
多态的好处:提高了程序的扩展性
class Fu {}class Zi extends Fu {\tpublic void method(){\t\t}}class Demo {\tpublic static void main(String[] args){\t\tFu f = new Zi();\t\t//f.method(); // 弊端: 不能调用子类特有的方法\t\tZi z = (Zi)f;\t\tz.method(); //正确的 可以调用。\t}}
举例
int a = 10;long lo = a;
自动类型转换(隐式转换)
long lo = 100L;int a = (int)lo;
强制类型转换
基本类型
class Animal {}class Cat extends Animal{}Cat c = new Cat(); //猫 (范围小)Animal a = c; // 自动类型转换, 多态 , 向上转换型。// Animal a = new Cat(); // 一样的
自动类型转换(向上转型)
Animal a = new Cat(); // 猫是一只动物, 但是你虽然看到的是动物 他本质是一只猫Cat c = (Cat)a; // 强制类型转换, 向下转型。Animal a1 = new Dog(); // 狗是一只动物 但是本质却是条狗//Cat c1 = (Cat)a1; // 运行报错 类型转换错误 // instanceof 前面要放一个 对象, 后面要放一个 类的名字// a instanceof A // a 是A的一个实例吗 如果是 那么这个表达式就是true 如果不是 那么这个表达式就是falseboolean b = a1 instanceof Cat;if (b){\tCat c1 = (Cat)a1;}
强制类型转换(向下转型)
解决:类型转换
多态的弊端:不能调用子类特有的内容
规范性: 代码中怎么体现规范性呢\tpublic interface Spile {\t\tpublic abstract void show(); // 接口的规范性 就体现在 抽象方法中, 因为子类 实现接口 必须要全部重写里面的抽象方法 而且方法声明一摸一样,你不一样 就报错, 所以这不就是强制性嘛。\t}\tclass Plug implements Spile {\t\tpublic void show(){\t\t\t\t}\t}
拓展性: 你只要按照接口的规则 去创建的, 就能和接口对接上,所有按照接口创建的 都可以对接上,所以这就是拓展性。\t代码中怎么插进去呢??\t这个必须要学习多态后可以能知道。
接口的两个作用:规范性、扩展性
接口开发中的应用举例:\t\t接口被设计为支持运行时动态方法解析。通常情况下,为了能够从一个类中调用另外一个类的方法,\t在编译时这两个类都需要存在,进而使Java编译器能够进行检查以确保方法签名是兼容的《Java8编程参考官方教程》\t\t// 模拟生活中的一个场景\t// 电脑可以 通过 usb接口 使用 鼠标设备。\t\t/*\t\tclass Computer {\t\tpublic void open(){\t\t\tSystem.out.println("电脑开机");\t\t}\t\tpublic void close(){\t\t\tSystem.out.println("电脑关机");\t\t}\t\tpublic void useDevice( Mouse m){\t\t\tm.run();\t\t}\t}*/\t\tinterface USB {\t\tvoid run();\t}\tclass Computer {\t\tpublic void open(){\t\t\tSystem.out.println("电脑开机");\t\t}\t\tpublic void close(){\t\t\tSystem.out.println("电脑关机");\t\t}\t\tpublic void useDevice( USB u){\t\t\t// USB u = new Mouse();\t\t\tu.run();\t\t}\t}\tclass Mouse implements USB {\t\t@Override\t\tpublic void run() {\t\t\tSystem.out.println("鼠标有左键 右键 运行起来啦");\t\t}\t}\tpublic class ComputerTest {\t\tpublic static void main(String[] args) {\t\t\tComputer c = new Computer();\t\t\tMouse m = new Mouse();\t\t\tc.useDevice(m);\t\t}\t}
美团外卖 --- 高德地图 \t\t\t\t\t\t\t\t高德地图 设计一套接口。 把这套接口 (class文件) 发给 美团。\t\t\t\t美团,把这套 接口(第三方类库) 导入到自己项目中, \t\t\t\t美团接下来开发的时候, 他需要调用高德地图的功能的时候, 调用接口就行了。(所以 美团开发项目的时候 不需要 高德地图的人员过来。)
接口的真实作用:解耦合
接口的作用
class Demo {\t//private static Student[] stus = new Student[5];\tprivate static ArrayList<Student> al = new ArrayList<>();\tpublic static void main(String[] args){\t\t// 判断数组是否满了\t\t// 判断是否重复 \t\t// 不满 不重复 才添加 \t\t//.....\t}}
传统不分包
专门和用户交互的 Controller\tclass StudentController {\t\tprivate StudentService ss = new StudentService();\t\tpublic void 界面(){\t\t\t// 让用户录入\t\t\t// 当用户录入 1 则表明添加学生 \t\t\t// 此时调用 本类的 addStudent()方法\t\t}\t\tpublic void addStudent(){\t\t\t// 录入学生\t\t\tboolean b = ss.addStudent(stu);\t\t\tif (b){\t\t\t\tSystem.out.println("添加成功");\t\t\t}\t\t}\t}专门操作业务的 Service\tclass StudentService {\t\t//private StudentDao sd = new StudentDao();\t\t//private OtherStudentDao sd = new OtherStudentDao();\t\tprivate BaseStudentDao sd = DaoFactory.getDao();\t\tpublic boolean addStudent(Student stu){\t\t\treturn sd.addStudent(stu);\t\t}\t}加一层 -----这一层的作用 用来获取 Dao的。\tclass DaoFactory {\t\tpublic static BaseStudentDao getDao(){\t\t\treturn new OtherStudentDao();\t\t}\t} // 工厂设计模式。专门操作数据的 Dao\tclass BaseStudentDao {\t\tpublic boolean addStudent(Student stu){\t\t\treturn true;\t\t}\t}\tclass StudentDao extends BaseStudentDao{\t\tprivate static Student[] stus = new Student[5];\t\t\t\tpublic boolean addStudent(Student stu){\t\t\t// 添加到数组\t\t\treturn true;\t\t}\t}\tclass OtherStudentDao extends BaseStudentDao {\t\tprivate static ArrayList<Student> al = new ArrayList<>();\t\tpublic boolean addStudent(Student stu){\t\t\t// 添加到集合\t\t\treturn true;\t\t}\t}
分包优化
多态优化学生管理系统
public class demo1 {\tpublic static void main(String[] args) {\t\tFu f = new Zi();\t\t//f.show(); // 报错 说明 不是重写\t\t//show 是私有的 只能在 class fu 里面去用的。\t\tf.method();\t}}class Fu{\tprivate void show(){\t\tSystem.out.println("fu");\t}\tpublic void method(){\t}}class Zi extends Fu{\tpublic void show(){\t\tSystem.out.println("zi");\t}}
1、私有的不算重写?
public class demo1 {\tpublic static void main(String[] args) {\t\tFu f = new Zi();\t\tf.show(); //fu\t}}class Fu{\tpublic static void show(){\t\tSystem.out.println("fu");\t}}class Zi extends Fu{\tpublic static void show(){\t\tSystem.out.println("zi");\t}}
2、静态的不算重写?
之前我们总说 重写父类的方法 方法声明必须一模一样。 其实返回值可以不一样。\t这个不一样,也是有关系的\t子类方法的返回值类型 必须 是 父类方法返回值类型的子类。
public class demo1 {\tpublic static void main(String[] args) {\t\tFu f = new Zi();\t\tPerson show = f.show();\t\tSystem.out.println(show); //test.day04.demo1.Student@f5f2bb7\t}}class Fu{\tpublic Person show(){\t\t//System.out.println("fu");\t\treturn new Person();\t}}class Zi extends Fu{\tpublic Student show(){\t\t//System.out.println("zi");\t\treturn new Student();\t}}class Person{}class Student extends Person{}\t\t
3、方法的声明一模一样?返回值可以不一样(子类返回值必须是父类返回值的子类)
历史遗留问题:方法的重写
多态
面向对象三大特性
private只能修饰成员,不能修饰局部,被private修饰的成员不能被外部访问
被private修饰的成员变量可以通过set/get方法进行赋值/访问
private
this代表的是对象,指的是一个“动态”对象。谁调用包含this的这个方法,谁就是this
this打破就近原则:局部变量是无法通过对象直接进行调用的
this
super
1、静态的东西 可以被该类所有的对象所共享 因为静态的东西是凡在方法区的静态区中
2、静态的东西 可以用对象名进行调用,也可以用类名进行调用,但是推荐用类名进行调用
3、静态的随着类的加载而加载进来的 。比创建对象要加载的早。非静态的随着对象的创建而存在,比静态加载的晚 因此,静态的不能直接访问非静态的。非静态的可以直接访问静态的。
4、静态的方法中不能有this——this指的是谁调用包含this的这个方法,随就是this,但是静态加载的比对象早,是随着类加载而加载的并没有上一级
目的是让你尽早的吧变量从内存中清理出去
局部代码块
每次调用构造方法,都会执行构造代码块,并且都是在构造方法之前执行
作用:可以把构造方法中反复的代码提取放到构造代码块中
构造代码块
随着类的加载而执行,而且仅执行唯一一次
静态代码块
5、静态代码块
static关键字注意事项:
static
能够修饰类,修饰的类不能被继承
能够修饰方法,被修饰的方法不能被重写
字面值常量:能够直接写出来的值
硬性要求:以数字、大小写字母、_、$组成
1、不能以数字开头
2、不能是关键字
软性要求:
常量的起名规则(回顾标识符)
class Demo {\tpublic static void main(String[] args){\t\tfinal int A = 10;\t\t//A = 20; //编译报错\t}}
自定义常量的形成
class Demo {\tpublic static void main(String[] args){\t\tfinal int A; //正确的。 局部常量使用之前必须先赋值。 只不过 赋值之后就不能再次赋值了\t\t\t\t/*int A;\t\tA = 20;\t\tSystem.out.println(A);\t\tA = 30;\t\tSystem.out.println(A);*/\t\t/*\t\tfinal int A;\t\tA = 20;\t\tSystem.out.println(A);\t\t//A = 30; // 编译报错。\t\t*/\t}}
局部常量:赋值之后不能重新赋值
案例1:\tclass Student{\t\tfinal int A; //A 是成员 有默认值 0 那你为什么给我报错啊?\t\t\t// 就是因为你有默认值才给你报错\t\t\t// 常量只能赋值一次, 如果你有默认值, 就相当于你已经让默认值赋值了。 今后就再也不能赋值了,一辈子只能用默认值了\t\t\t// 但是你要知道 默认值 是用来占位的\t\t\t// 不是为了让你去用的。 想让你珍惜这个常量的赋值机会。\t}
案例2:\tclass Student{\t\tfinal int A = 10; //正确了。\t}
案例3:\tclass Teacher {\t\tfinal int A = 20;\t}\tclass Student{\t\tfinal int A = 10; \t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\t//System.out.println(A);// 编译报错\t\t\t//System.out.println(Student.A); // A 被final修饰 并不是被static修饰啊。 所以不能用类名调用。\t\t\tStudent s = new Student();\t\t\tSystem.out.println(s.A); //10 \t\t\t\t// 我在使用这个A之前 A必须不能是默认值。 只要在A存在之前 A是绝对不能是默认值的。\t\t\t\t// 也就是说 在使用A之前 而A只能是由对象来去调用,所以 在Student 创建学生对象之前 你能够把A的默认值改掉就行了。\t\t\t\t// 不非得 定义的时候 直接写死。 final int A = 10; \t\t\t\t// 你想啊 创建对象之前都做了哪些事情啊?\t\t\t\t// 静态代码块 构造代码块 构造方法\t\t}\t}
案例4:\tclass Student {\t\tpublic final int a;\t\t\t\t{ //构造代码块\t\t\ta = 20; //正确\t\t}\t}
案例5:\tclass Student {\t\tpublic final int a =10;\t\t\t\t{ \t\t\ta = 20; //编译报错 final修饰的数据 只能赋值一次。 而你现在赋值两次\t\t}\t}
案例6:\tclass Student {\t\tpublic final int a ;\t\t\t\tstatic { \t\t\ta = 20; //肯定编译报错 因为 static 是先人 a是后人 先人不能访问后人的东西\t\t}\t}
案例7:\tclass Student {\t\tpublic static final int a ;\t\t\t\tstatic { \t\t\ta = 20; // 正确\t\t}\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\tSystem.out.println(Student.a);\t\t}\t}
案例8:\tclass Student {\t\tpublic static final int a ; // \t\t\t\t{ \t\t\ta = 20; // 构造代码块是后人 a是先人 虽然可以访问 但是 a是静态的 可以用类名使用, 因为在类加载的时候去把 默认值取消掉。\t\t\t\t\t//而构造代码块只能在创建对象的时候执行,已经晚了。\t\t}\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\tSystem.out.println(Student.a); \t\t}\t}
案例9:\tclass Student {\t\tpublic final int a ;\t\t\t\tpublic Student(){\t\t\ta = 10; // 正确\t\t}\t}
案例10:\tclass Student {\t\tpublic final int a ;\t\t{\t\t\ta = 20;\t\t}\t\tpublic Student(){\t\t\ta = 10; // 编译报错 因为赋值了两次。\t\t}\t}
案例11:\tclass Student {\t\tpublic final int a=20 ;\t\t\tpublic Student(){\t\t\ta = 10; // 编译报错 因为赋值了两次。\t\t}\t}
案例12:\tclass Student {\t\tpublic final int a;\t\t\t\tpublic Student(int age){\t\t\ta = age;\t\t}\t\tpublic Student(){\t\t\ta = 10; \t\t}\t}\t\tclass Demo {\t\tpublic static void main(String[] args){\t\t\t\t\t\tStudent s = new Student(20);\t\t\tSystem.out.println(s.a); //20\t\t\t//s.a =30; //编译报错\t\t\tStudent s = new Student(30);\t\t\tSystem.out.println(s.a); //30\t\t}\t}
案例13:\tclass Student {\t\tpublic final int a ; //编译报错\t\t\t\tpublic Student(int age){\t\t\t\t\t}\t\tpublic Student(){\t\t\ta = 10; \t\t}\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\tStudent s = new Student();\t\t\tSystem.out.println(s.a); //10\t\t\tStudent s = new Student(20);\t\t\tSystem.out.println(s.a); //只能是默认值 而成员常量 不能是默认值、\t\t}\t}
案例14:\t\tclass Student {\t\tpublic final int a ; \t\t\t\t{\t\t\ta=20;\t\t //正确\t\t\t\t\t\t\t\t}\t\tpublic Student(int age){\t\t\t\t\t}\t\tpublic Student(){\t\t\t\t\t}\t}
案例15:\tclass Student {\t\tpublic final int a =20; // 正确\t\tpublic Student(int age){\t\t\t\t\t}\t\tpublic Student(){\t\t\t\t\t}\t}
案例16:\tclass Student {\t\tpublic final int a; // 正确\t\tpublic Student(int age){\t\t\ta=20;\t\t}\t\tpublic Student(){\t\t\tthis(10);\t\t}\t}
成员常量
自定义常量的分类
自定义常量:被final修饰的变量,就变成了常量,只能赋值一次
能够修饰变量,修饰的变量变成了常量(只能赋值一次)
final
定义:一个类中如果出现类使用abstract关键字修饰的方法,则是抽象方法,那么这个类就必须定义为抽象类。
如果一个普通类中包含一个抽象方法,而普通类是可以创建对象的。 Animal a = new Animal(); a.show(); //show方法是抽象的,没有方法体的,执行是没有任何的意义,因此是矛盾的 所以java的设计者 针对这个矛盾 就提出了语法 如果一个类中包含抽象方法,这个类就必须定义为抽象类。 因为抽象类是没有办法创建对象的。
为什么包含抽象方法的类必须是抽象类?
(1)、为什么抽象类不能创建对象? 因为抽象类中可能存在抽象方法,如果允许抽象类创建对象,那么当对象去调用重选ing方法时,却没有任何的意义,因此互相矛盾
抽象类是有构造方法的, 他的构造方法 并不是为了让他创建对象用的, 而是用来让子类继承他的时候 初始化他的。
那么究竟是谁起到了 抽象不能创建对象的作用呢 abstract 这个关键字
(2)、抽象不能创建对象是不是因为它没有构造方法?
1、抽象类不能创建对象
2、抽象类里面 可以没有抽象方法,但是有抽象方法的类 必须是抽象类
注:抽象类继承抽象类 就不是必须要重写了
3、普通类继承抽象类,则必须重写里面的全部抽象的方法,不重写就报错。
特点:
抽象类:
数组 [ ]
\t\tpublic interface Spile {\t\t\t\t}
定义
\t\t\t\tabstract class Person {\t\t\t\t\tpublic abstract void show();\t\t\t\t}\t\t\t\t// Person p = new Person(); p.show(); //无内容 怎么调用 矛盾了。\t\t\t\t// abstract 修饰的类 就是不能创建对象。\t\t\t\t//抽象类创建不了对象 是不是因为他没有构造方法啊。\t\t\t\tclass Student extends Person {\t\t\t\t\tpublic Student(){\t\t\t\t\t\tsuper();\t // Person的无参构造 说明 抽象类是有构造方法的。\t\t\t\t\t}\t\t\t\t}
抽象类不能创建对象
\t\t\t\tinterface Spile {\t\t\t\t\tpublic abstract void show();\t\t\t\t}\t\t\t\t//Spile s = new Spile(); s.show(); //无内容 怎么调用 矛盾了。\t\t\t\t// interface 接口不能创建对象。\t\t\t\t//接口不能创建对象 会不会是因为他没有构造方法呢?\t\t\t\tclass Student /*extends Object*/implements Spile {\t\t\t\t\tpublic Student(){\t\t\t\t\t\tsuper();\t //访问的Object的无参构造 证明了接口没有构造方法。\t\t\t\t\t}\t\t\t\t}
接口为什么不能创建对象?
1、接口不能创建对象
回顾单继承
既然上面的矛盾 那下面这个例子呢?
接口为什么能多实现?
2、子类和接口的关系是实现关系,而且可以多实现
\t\t\t\tinterface Spile {\t\t\t\t\tpublic abstract void show();\t\t\t\t}\t\t\t\tclass Student implements Spile {\t\t\t\t\t\t\t\t\t}\t\t\t\t\t\t\t\tStudent s = new Student();\t\t\t\ts.show(); // 内容呢?? 矛盾了。
根据以上 ,推导以下案例:
\t\t\t\tinterface Spile {\t\t\t\t\tpublic abstract void show();\t\t\t\t}\t\t\t\tabstract class Student implements Spile { //正确的\t\t\t\t\t\t\t\t\t}\t\t\t\t//Student s = new Student(); //不能创建对象
为什么子类要全部重写接口中的全部抽象方法?
3:子类实现接口 必须重写里面的所有抽象方法。 而这也正是接口具有规范性的体现。
\t\t\t\tpublic interface Spile {\t\t\t\t\tpublic abstract void show(); // 接口的规范性 就体现在 抽象方法中, 因为子类 实现接口 必须要全部重写里面的抽象方法 而且方法声明一摸一样,你不一样 就报错, 所以这不就是强制性嘛。\t\t\t\t}\t\t\t\tclass Plug implements Spile {\t\t\t\t\tpublic void show(){\t\t\t\t\t\t\t\t\t\t}\t\t\t\t}
规范性:
你只要按照接口的规则 去创建的, 就能和接口对接上,所有按照接口创建的 都可以对接上,所以这就是拓展性。\t\t\t\t代码中怎么插进去呢??\t\t\t\t这个必须要学习多态后可以能知道。
扩展性:
4:接口的作用 有两个 规范性和扩展性
注: 接口的真实作用其实是解耦!
接口特点
接口没有构造方法!
案例1:\tinterface Law {\t\tint a= 10; //正确 即使你什么修饰符都不写 那么这个变量前面也会有默认的修饰符 public static final\t}
案例2:\tinterface Law {\t\tint a; // 编译报错 常量 不能是用默认值 \t}\tinterface Law {\t\tint a; \t\tpublic Law(){ // 编译报错 接口没有构造方法\t\t\ta = 20;\t\t}\t}\tinterface Law {\t\tint a; \t\t{ //为什么叫做构造代码块 因为他是为构造方法而服务的, 接口中没有构造方法。 所以 java规定 接口中不能存在构造代码块的\t\t\ta = 20;\t\t}\t}\tinterface Law {\t\tint a; \t\tstatic { //编译报错 静态代码块 不允许存在于接口中, 是因为 早期jdk1.7之前 接口就是作为一种规范 不想让他具备有逻辑的语句。\t\t\ta = 20;\t\t}\t}\tinterface Law {\t\tint a =10; \t}
接口中的成员变量 全部都是 常量 前面都有默认修饰符 public static final
jdk1.8之前:接口中的方法 只能有抽象方法 即使你什么修饰符都不写 也有默认的 public abstract
案例1:\tinterface Dao {\t\t/*\t\tvoid show(){ //如果你要 直接什么修饰符都不加 系统默认给你加 public abstract\t\t\t//编译报错\t\t}\t\t*/\t\tdefault void show(){\t\t\tSystem.out.println("接口中就具备了 具有功能的方法");\t\t}\t}
案例2:\tinterface Dao {\t\tdefault void show(){\t\t\tSystem.out.println("接口中就具备了 具有功能的方法");\t\t}\t}\tclass StudentDao implements Dao {\t\t\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\t//Dao.show(); // 编译报错 show是静态的吗 不是 就不能用类名去调用。\t\t\t//Dao d = new Dao();\t\t\t//d.show(); // 编译报错 接口不能创建对象\t\t\t\t\t\t//StudentDao.show(); // 编译报错。\t\t\tStudentDao sd = new StudentDao();\t\t\tsd.show(); //正确 对\t\t}\t}
案例3:\tinterface Dao {\t\t\t\t/*public */default void show(){\t\t\tSystem.out.println("接口中就具备了 具有功能的方法");\t\t}\t}\tclass StudentDao implements Dao {\t\tvoid show(){ //编译报错 子类重写父类的方法 权限修饰符必须大于等于父类权限修饰符。\t\t\tSystem.out.println("重写show方法");\t\t}\t}
出现默认方法
类中的静态方法\tclass Person {\t\tpublic static void show(){\t\t\tSystem.out.println("person");\t\t}\t}\tclass Student extends Person {\t\t\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\tStudent s = new Student();\t\t\ts.show(); // 可以调用\t\t\tStudent.show(); // 可以调用\t\t\tPerson p = new Person();\t\t\tp.show(); //可以调用\t\t\tPerson.show(); //可以调用 (推荐)\t\t}\t}
接口中的静态方法\tinterface Dao {\t\t\t\tstatic void show(){\t\t\tSystem.out.println("接口static方法");\t\t}\t}\tclass StudentDao implements Dao {\t\t\t}\tclass Demo {\t\tpublic static void main(String[] args){\t\t\tStudentDao sd = new StudentDao();\t\t\t//sd.show(); // 编译报错。\t\t\t\t\t\tStudentDao.show(); // 编译报错\t\t\tDao d = new Dao(); //编译报错\t\t\td.show(); \t\t\tDao.show(); // 正确 \t\t\t// 接口中的 static方法 是给自己准备的 子类无论如何调用不了。\t\t}\t}
出现静态方法
jdk1.8之后
public interface Dao {\tdefault void show(){\t\tSystem.out.println("haha");\t\tchongfu();\t}\tprivate void chongfu(){\t\tSystem.out.println("1");\t\tSystem.out.println("2");\t\tSystem.out.println("3");\t\tSystem.out.println("4");\t\tSystem.out.println("5");\t}\tdefault void method(){\t\tSystem.out.println("xixi");\t\tchongfu();\t}\tstatic void show1(){\t\tSystem.out.println("haha");\t\tchongfu1();\t\t//chongfu();\t}\tprivate static void chongfu1(){\t\tSystem.out.println("1");\t\tSystem.out.println("2");\t\tSystem.out.println("3");\t\tSystem.out.println("4");\t\tSystem.out.println("5");\t}\tstatic void method1(){\t\tSystem.out.println("xixi");\t\tchongfu1();\t}}
jdk1.9之后 :出现私有方法
成员方法
接口的成员
class YeYe {\tpublic void show(){\t\tSystem.out.println("YeYe");\t}}class Fu extends YeYe{\tpublic void show(){\t\tSystem.out.println("YeYe");\t}}class Zi extends Fu {}Zi z = new Zi();z.show(); // 调用的父类的
类和类:继承关系,可以单继承,不能多继承,但可以多层继承
案例3:\tinterface Dao {\t\tdefault void show(){\t\t\tSystem.out.println("abc");\t\t}\t}\tclass Person {\t\tpublic void show(){\t\t\tSystem.out.println("bcd");\t\t}\t}\t/*\tclass Student implements Dao extends Person { //编译报错\t\t}\t*/\tclass Student extends Person implements Dao {\t\t}\tStudent s = new Student();\ts.show(); //bcd\t// java规定 当一个类中的方法 和 接口中的 默认方法方法声明一样的时候, 子类优先使用父类的。
案例4:\tinterface Dao {\t\tpublic abstract void show();\t}\tclass Person {\t\tpublic void show(){\t\t\tSystem.out.println("bcd");\t\t}\t}\tclass Student extends Person implements Dao {\t\t// 正确\t\t// Student 没有重写 Dao的 show方法也不报错。\t\t// 因为你继承的Person里面有一个show\t\t// 你继承了Person 不就相当于你自己具备了一个show嘛 也就算重写了\t}
类和接口:实现关系,可以单实现,也可以多实现
接口和接口:继承关系,可以单继承,也可以多继承
类和接口的关系
设计理念不同!
抽象类和接口的区别
内部类访问外部类的内容 直接可以访问的,包括私有的。外部类要想访问内部类的内容, 必须创建内部类的对象 对象调用。
public class Outer {\tprivate int age = 10;\tpublic class Inner {\t\t\t\tpublic int leg = 5;\t\tprivate int hehe = 20;\t\t//System.out.println(age); // 编译报错 除了定义变量类的语句 可以直接写在成员位置(成员变量) 其他所有的语句 比如 打印语句 if语句 for语句都要写在方法里面\t\t\t\tpublic void show(){\t\t\tSystem.out.println(age); // 正确 为什么可以访问 age 的private的作用范围 是 Outer内, show就是在outer内的。\t\t\tSystem.out.println(leg); // 正确 就近原则。\t\t}\t}\tpublic void method(){\t\tSystem.out.println(age); //正确 \t\t// System.out.println(age); // 其实这句话底层是这么写的 System.out.println(this.age);\t\tSystem.out.println(leg); // 编译报错 \t\t\t// this.leg Outer的成员位置 是没有leg的。\t\t\t// leg 是非静态的成员变量 需要用对象来调用的。 leg是 Inner类的成员变量 那么就需要用Inner的对象来调用\t\tInner in = new Inner();\t\tSystem.out.println(in.leg); //正确\t\tSystem.out.println(in.hehe); // 正确 private修饰的hehe 不仅能在Inner 里面去访问的,也可以在外部类的内部访问, 因为private的权限可以扩大到最外层的那个类的内部。\t\tSystem.out.println(xixi); //编译报错 xixi在function的方法里面 他是局部变量 只在局部有效。 是没有办法 从一个局部 直接调用另一个局部的东西\t\t//function f = new function(); \t\t//System.out.println(f.xixi);\t\tint a = getX();\t\tSystem.out.println(a); // 正确。\t}\tpublic void function(){\t\tint xixi = 30;\t}\tpublic int getX(){\t\tint x = 10;\t\treturn x;\t}}
访问特点
public class Outer {\t// 成员位置 去定义类 : 成员内部类public void show(){\t// 局部位置 去定义类 : 局部内部类。\t}}
\tpublic class Outer {\t\tint a = 10;\t\tpublic class Inner{\t\t\tpublic void method(){\t\t\t\tSystem.out.println("内部类的method方法");\t\t\t}\t\t}\t}\tpublic class Test {\t\tpublic static void main(String[] args) {\t\t\t//创建内部类对象。\t\t /* Outer.Inner oi = new Outer().new Inner();\t\t\toi.method();*/\t\t\t//Inner i = new Outer().new Inner();\t\t\t/*\t\t\tOuter.Inner 这么写是因为你直接写 Inner 找不到 Inner在哪\t\t\t\t你能找到的是Outer类。 所以用Outer 限定一下, 告诉jvm Inner是在Outer里面\t\t\toi 是变量名 随便起名。\t\t\tnew Outer().new Inner(); :这儿为什么是这样写的呢?\t\t\t\t我想请问你一下。Inner是属于Outer的成员。 非静态的成员,随着对象的存在而存在的。\t\t\t\t如果没有Outer对象,请问 Outer对象的里面的成员 能不能用》??? 不能使用。\t\t\t\tInner 是Outer的成员。\t\t\t\t所以 要想有Inner的对象, 请必须先给我Outer的对象。\t\t\t\t所以写法 new Outer().new Inner();\t\t\t\t所以现在把这个写法修改一下 你也就理解了。\t\t\t */\t\t\tOuter o = new Outer();\t\t\tOuter.Inner oi = o.new Inner();\t\t}\t}
1、内部类创建对象的格式理解
\tpublic class Outer {\t\tint a = 10;\t\tpublic static class Inner{\t\t\tpublic void method(){\t\t\t\tSystem.out.println("内部类的method方法");\t\t\t}\t\t}\t}\tpublic class Test {\t\tpublic static void main(String[] args) {\t\t\t\t\t\t\t\tOuter.Inner oi = new Outer.Inner();\t\t\t/*\t\t\tInner 是属于 Outer的静态内容。\t\t\t静态的随着类的存在而存在的, 所以可以用类名调用。\t\t\t */\t\t}\t}
2、静态的成员内部类
\tpublic class Outer {\t\tint a = 10;\t\tpublic class Inner{\t\t\tint a = 20;\t\t\tpublic void method(){\t\t\t\tint a = 30;\t\t\t\tSystem.out.println(a); // 30\t\t\t\tSystem.out.println(this.a); // 20\t\t\t\t//System.out.println(super.a); //编译报错\t\t\t\t//System.out.println(Outer.a); //编译报错\t\t\t\tSystem.out.println(Outer.this.a); // 5\t\t\t\tSystem.out.println(new Outer().a); //10\t\t\t}\t\t}\t\tint b = 40;\t}\tpublic class Test {\t\tpublic static void main(String[] args) {\t\t\tOuter o = new Outer();\t\t\to.a = 5;\t\t\tOuter.Inner oi = o.new Inner();\t\t\toi.method();\t\t\t/*\t\t\tInner 他是不是属于 Outer的成员。\t\t\t成员随着 对象存在的。\t\t\t所以 Inner对象的存在 必须 随着 Outer对象的存在而存在。\t\t\t开发中 单纯的Outer对象 是不是可以有很多个啊?\t\t\t我只想找到 Inner对象所在的 Outer对象。\t\t\t */\t\t}\t}
3、成员内部类访问当前外部类对象的内容
\tpublic interface Breast {\t\tvoid beat();\t}\tpublic class Person {\t\tprivate class Heart implements Breast {\t\t\t@Override\t\t\tpublic void beat() {\t\t\t\tSystem.out.println("心脏在跳动");\t\t\t}\t\t}\t\tpublic Heart showBreast(){\t\t\tHeart h = new Heart();\t\t\treturn h;\t\t}\t}\tpublic class Test {\t\tpublic static void main(String[] args) {\t\t\t//Person.Heart; 编译报错,原因是因为访问不了\t\t\tPerson per = new Person();\t\t\tBreast breast = per.showBreast(); // new Heart();\t\t\tbreast.beat();\t\t}\t}
4、私有成员内部类
成员内部类
class Outer { //私有成员变量 private int num = 10; //成员方法 public Face show() { class Inner implements Face { public void method() { System.out.println(num); } } Inner i = new Inner(); i.method(); // 如果你觉得此处多此一举。 // 那么你就想办法 让外界去使用 Inner对象。 // 也就是想办法 把 Inner 对象扔出方法去 // 怎么从方法扔出去呢? 方法的返回值就可以做到。 // 那么怎么定义 方法的返回值类型 就可以把Inner对象扔出去呢?? // 如果要把 Inner 对象扔出去, 返回值类型 定义为 Inner类型就可以了 // 但是 Inner 是局部的东西,不能直接在外面写。 // 这时候我们只能改变策略。 // 返回值类型 是Inner类型的时候 我们返回一个Inner对象, 但是 返回值类型是Inner的父类 或者父接口类型的时候,我们依然也可以返回一个Inner对象 return i; }}interface Face { void method();}public class Test { public static void main(String[] args) { //Outer.Inner oi = new Outer().new Inner(); 编译报错 //不可能 // Inner 是局部的。 /* 局部的东西的 生命周期 随着方法的调用而存在 随着方法调用完毕而结束。 */ /* Outer o = new Outer(); o.show(); */ //Outer.Inner Outer o = new Outer(); Face f = o.show(); // 你接收出来了Inner之后, 想用Inner干嘛 //是不是想用他里面的method方法。 f.method(); }}
局部内部类将对象丢到外界使用:
public class Person { public Digests digestion(){ int a = 10; //a = 20; class Tract implements Digests{ public void diges(){ System.out.println("肠道消化 java"); System.out.println(a); //编译报错, 因为a必须是常量才行。 } } Tract t = new Tract(); return t; }}interface Digests { public void diges();}
局部内部类访问局部变量,局部变量前面必须有final进行修饰, 在jdk1.8的时候final可以省略,但并不是真正的省略,而是前面会默认添加final关键字,这样做的是为了延长局部变量的生命周期
面试题(☆☆☆☆):
public class Test { public static void main(String[] args) { // 想使用Face 调用一下 show方法。 //第一种方案。 //新建 java文件 创建一个类 实现接口 重写方法。 // 创建该类对象,调用方法。 Face f = new MyFace(); f.show(); //第二种方案:因为第一种方案 我觉得 还要建一个java文件 怪麻烦的。 // 创建一个类 实现接口 重写方法。 // 创建该类对象,调用方法。 class MyFace1 implements Face { public void show() { System.out.println("你的脸有几分憔悴 你的眼有残留的泪"); } } Face mf = new MyFace1(); mf.show(); //第三种方案 :我觉得还要建一个类,然后才能调用这个show方法。我觉得建类好麻烦。 // 匿名内部类就好了。 Face f1 = new // 这是在创建下面的匿名内部类的 对象。 Face() { // 建一个内部类,只不过这类 没有名字, 所以叫做 匿名的内部类\t\t\tpublic void show() {\t\t\t\tSystem.out.println("你的脸有几分憔悴 你的眼有残留的泪");\t\t\t}\t\t}; f1.show(); \t\t/*\t\t匿名内部类的格式:\t\t\tnew 接口名(){\t\t\t\t重写方法;\t\t\t};\t\t */ }}class MyFace implements Face { @Override public void show() { System.out.println("你的脸有几分憔悴 你的眼有残留的泪"); }}
匿名内部类的由来
interface Digests { public void diges();}class Person { public Digests digestion() { /*class Tract implements Digests{ public void diges(){ System.out.println("肠道消化 java"); } }*/ /* Tract t = new Tract(); return t; */ //return new Tract(); return new Digests() { @Override public void diges() { System.out.println("肠道消化 java"); } }; }}
匿名内部类的应用:匿名内部类一般会作为方法的实参去传递,返回值去返回
匿名内部类(☆☆☆☆☆)
interface Inter { void function();}class Demo { public static void main(String[] args) {\t\t/*\t\tInter inte = new Inter(){\t\t\tpublic void function(){\t\t\t\tSystem.out.println("function");\t\t\t}\t\t};\t\tshow(inte);\t\t*/ show(new Inter() { public void function() { System.out.println("function"); } }); } public static void show(Inter in) { in.function(); }}
当一个接口,只有一个抽象方法的时候,使用匿名内部类是比较合适的,因为这样,使用匿名内部类是比较方便的,建议少建Java文件、少写class类
匿名内部类的应用场景
public class Demo3 { public static void main(String[] args) { new Dao() { @Override public void findAll() { System.out.println(\"abc\"); } }; // lambda表达式用来 优化匿名内部类 // lambda表达式的格式 是:() -> { 重写方法的内容} // 格式含义: // () 代表是的 你这个接口中的那个抽象方法的 参数 也就是 这个例子中 findAll()方法的参数。 // -> 这是一个固定格式 意思是 ()里面的参数 可以给 后面的大括号{}使用。 // {} 就是要写 你重写方法的 那个方法体。 //根据()格式的定义 我们就能推断出 接口只能有一个抽象方法的时候 才可以使用 lambda表达式。否则有的方法有参数 有的方法没有参数,那我的()内要不要写参数呢? //必须有上下文的推导, jvm编译过程中 必须要给jvm一个逻辑 根据这个逻辑 可以推导出 这个lambda表达式 是在实现的哪个类。 () -> { System.out.println(\"abc\"); }; // 编译报错 因为不知道实现的哪个接口。 Dao d1 = () -> { System.out.println(\"abc\"); }; // 编译正确 有上下文推导 show(() -> { System.out.println(\"abc\"); }); // 编译正确。 有上下文推导 Service s = () -> { System.out.println(\"哈哈哈\"); }; // 编译报错 } public static void show(Dao d) { d.findAll(); }}interface Dao { void findAll(); //void show(int a);}interface Service { void show(); void function(int a);}
lambda表达式 的格式说明和前提条件
1、()内如果只有一个参数 可以省略数据类型和小括号本身
2、()内多个参数的 只能省略参数的数据类型 ,不能省略小括号的本身
3、()内如果只有一句话 可以省略分号 还有return 还可以省略大括号本身
lambda优化
可以堆普通类、抽象类、接口都可以使用 同时里面可以有多个抽象方法
匿名内部类会生成class文件
匿名内部类:
lambda只能对接口使用
lambda要求接口中只能有一个抽象方法
lambda不会生成class文件
lambda:
lambda和匿名内部类的区别
Lambda表达式
局部内部类
内部类分类
内部类:
private : 给自己准备的
默认的 :给自己家准备的。
protected : 给自己家准备的, 同时也给你在远方的儿子准备的。
public : 给世界准备的。
权限修饰符
异常:
泛指的是 泛泛的 类型 泛指某一种类型(必须是引用类型)
1、 创建一个对象的时候明确泛型
2、调用一个方法 的时候明确泛型
3、创建一个类 然后类实现一个接口的时候 明确泛型
泛型的创建时机:
泛型的概述
1、能让程序更加的精确 、准确 把一些运行时的错误在编写代码的时候 去避免掉
2、提高程序的 可扩展性 兼容性
泛型的好处
//泛型定义在类上的 叫做泛型类 泛型在类中都可以使用public class Display1<T> { public void show(T t){ System.out.println(t); }}
泛型类:
泛型方法:
//泛型接口 可以在实现类中明确泛型的类型public interface Display3<T> { void show(T t);}class Display3Impl implements Display3<String> { @Override public void show(String s) { System.out.println(s); }}//在实现类中实现泛型接口时,可以延用泛型 在创建对象时再指定泛型类型class Display3Impl2<T> implements Display3<T> { @Override public void show(T t) { System.out.println(t); }}
泛型接口:
泛型的分类
public class Demo05 { public static void main(String[] args) { //ArrayList<?> list = new ArrayList<>(); ArrayList<Integer> list = new ArrayList<>(); //list.add(\"dfa\"); //泛型为 ? 的对象 并不是用力啊接受数据的 而是用来接受数据类型不确定的集合 list.add(12); list.add(64); list.add(13); list.add(24); show(list); } public static void show(ArrayList<?> list){ for (Object o : list) { System.out.println(o); } }}
<? & >
public class Demo06 { public static void main(String[] args) { ArrayList<Cat> list1 = new ArrayList<>(); Cat cat = new Cat(); list1.add(cat); //<? extends E> show(list1); System.out.println(\"------------>\"); //<? super E> show02(list1); System.out.println(\"----------->\"); ArrayList<WanCat> list2 = new ArrayList<>(); WanCat wanCat = new WanCat(); list2.add(wanCat); show(list2); System.out.println(\"------------>\"); //show02(list2); ArrayList<Animal> list3 = new ArrayList<>(); Animal animal = new Animal(); list3.add(animal); //show(list3); show02(list3); } public static void show(ArrayList<? extends Cat> list){ for (Cat cat : list) { cat.show(); } } public static void show02(ArrayList<? super Cat> list){ for (Object o : list) { System.out.println(o); } }}
<? extends >
<? super >
泛型通配符:
泛型 jdk1.5的新特性:
Java基础语法
next()
解决方法1:\t\t\t\t\tpublic class Demo1 {\t\t\t\t\t\tpublic static void main(String[] args) {\t\t\t\t\t\t\tScanner sc = new Scanner(System.in);\t\t\t\t\t\t\tint anInt = sc.nextInt();\t\t\t\t\t\t\tString next = sc.next(); //第一种解决方案: 用next来代替nextLine就可以了。\t\t\t\t\t\t\t//但是next是以 "空格类字符"为结束标记的。 当你录入abc bcde 的时候,那么 直接接收 abc\t\t\t\t\t\t}\t\t\t\t\t}
解决方案2:\t\t\t\t\tpublic class Demo1 {\t\t\t\t\t\tpublic static void main(String[] args) {\t\t\t\t\t\t\tScanner sc = new Scanner(System.in);\t\t\t\t\t\t\tint anInt = sc.nextInt();\t\t\t\t\t\t\tsc = new Scanner(System.in);\t\t\t\t\t\t\tString next = sc.nextLine(); //第二种解决方案: 使用完 nextInt之后, 新创建一个新的Scanner对象, 用新的Scanner 对象去调用nextLine就可以了。\t\t\t\t\t\t\t// 但是 这种 的缺点就是 浪费空间。\t\t\t\t\t\t}\t\t\t\t\t}
bug:先nextInt 后nextLine的时候 就会出现nextLine不能录入的情况
nextLine()
nextInt()
......
Scanner
Random
字符串是常量:在基本数据(常量)中,只有String是引用类型。几乎所有的引用类型中都是综合数据,只有String表示的基本数据。其他的引用类型 必须要new才是一个对象。 但是String直接写一个 基本数据"abc"字符串常量,它就是一个String的对象。
System.out.println(10);\t\t\t\t\tSystem.out.println(20);\t\t\t\t\tint a = 10;\t\t\t\t\ta = 20;\t\t\t\t\tSystem.out.println("abc");\t\t\t\t\tSystem.out.println("bcd");\t\t\t\t\tString s = "abc";\t\t\t\t\ts = "bcd";
字面值常量:运行过程中,数据不可改变
String的特点:
String s = "abc";\t\t\t\t\tString s1 = "abc";\t\t\t\t\tSystem.out.println(s.equals(s1)); // true equals比较的是内容 和地址无关。\t\t\t\t\tSystem.out.println(s == s1); // true 地址值一样
\t\t\t\t\tString s = "abc";\t\t\t\t\tString s1 = new String("abc");\t\t\t\t\tSystem.out.println(s.equals(s1)); //true 内容\t\t\t\t\tSystem.out.println(s == s1); //false 地址
\t\t\t\t\tString s = "abc";\t\t\t\t\tString s1 = "ab"+"c"; // 常量优化\t\t\t\t\tSystem.out.println(s.equals(s1)); //true 内容\t\t\t\t\tSystem.out.println(s == s1); //true
\t\t\t\t\tString s = "abc";\t\t\t\t\tString s2 = "ab";\t\t\t\t\tString s1 = s2 +"c"; // 变量s2 和常量"c" 没有常量优化机制了。\t\t\t\t\tSystem.out.println(s.equals(s1)); //true 内容\t\t\t\t\tSystem.out.println(s == s1); //false 底层 创建一 StringBuilder 把ab和c拼接起来,然后再通过 new String的方法 转回 "abc"
字符串的常见面试题
String s4 = "abc";
String s = new String(); // String s = ""; 无参构造方法
String s1 = new String("abc"); // String s = "abc";
public char charAt(int index) 返回index位置上的 那个字符。
public boolean equals(String str); 调用equals的字符串 和 参数上的字符 比较字符串里面的内容, 如果内容相同,则返回true 如果内容不同则返回false
public boolean equalsIgnoreCase(String str);调用equalsIgnoreCase的字符串 和 参数上的字符 比较字符串里面的内容()不区分大小写, 如果内容相同,则返回true 如果内容不同则返回false
public int length(); 返回字符串的长度
public char[] toCharArray(); 把字符串里面的每个字符拆开 放到一个字符数组中 并返回。
public String substring(int start); // 从字符串的start这个索引开始 截取到最后 返回成新的字符串。
和replaceAll的区别在于, replaceAll 只能传字符串, 而这个方法可以传字符串也可以传字符。
public String[] split(String regex); // 根据regex 这个小串 把大串切割出来。
boolean contains(CharSequence s) 判断当前字符串是否包含指定的字符串 包含返回为true
String
StringBuilder sb = new StringBuilder(); //无参
StringBuilder sb = new StringBuilder("abc");
public StringBuilder append(任意类型);//拼接字符串
public StringBuilder reverse();//反转字符串
public int length(); 获取StringBuilder里面字符的个数
public String toString();//转换为String类型
1: \t\t\t\t\tString s = "abc";\t\t\t\t\tStringBuilder sb = new StringBuilder(s);
2:\t\t\t\t\tString s = "abc";\t\t\t\t\tStringBuilder sb = new StringBuilder();\t\t\t\t\tsb.append(s);
String --> StringBuilder
\t\t\t\t\tStringBuilder sb = new StringBuilder();\t\t\t\t\tsb.append(true);\t\t\t\t\tString s = sb.toString(); //转换\t\t\t\t\tSystem.out.println(s); //true
StringBuilder --> String
String和StringBuilder互相转换
String拼接字符串原理
StringBuilder拼接字符串原理
提高拼接效率的原理
注:打印 char[] String StringBuilder ArrayList 这些打印的时候 直接打印内容 而不是地址
StringBuilder
ArrayList
注:Math的方法都是静态的,构造方法是私有的。Math不能创建对象
public static int abs(int a){ //返回参数的绝对值 absolute 绝对的 return (a>0)? a : (a<0?-a:a); }
public static int abs(int a)\t\t返回参数的绝对值 absolute 绝对的
public static double ceil(double a){//向上取整 比如 a如果是3.1 则返回 4 double num = a*10%10; if (num>0){ a = (a*10-num)/10+1; } return a; }
public static double ceil(double a)\t向上取整
public static double floor(double a){//向下取整 比如 a如果是3.9 则返回 3 double num = a*10%10; if (num>0){ a = (a*10-num)/10; } return a; }
public static double floor(double a)\t向下取整
public static int round(float a){ //四舍五入 比如 a如果是 3.499 则返回3 比如a如果是3.50001 则返回4 int i = (int) (a*10%10); int num = 0; if (i>=5){ num = (int) a+1; }else { num = (int) a; } if (a<0){ num = (int) a+1; } return num; }
public static int round(float a)\t四舍五入
public static int nextInt(int a){ long start = System.currentTimeMillis(); int num = (int)(Math.random()*a); if (num != 0){ lo:while (true){ num = (int)(Math.random()*a); if (num == 0){ break lo; } } } long end = System.currentTimeMillis(); System.out.println(end - start); return num; }
Math
注:System的方法都是静态的 System的构造方法是私有的。
0表示的正常退出虚拟机 非0的表示的异常退出。
public static void exit(int status) 终止当前运行的Java虚拟机
public Static long currenTimeMillis() 返回的是一个long类型的计算机时间原点 到当前时间 的毫秒值
System
toString: 类从写了同String ,打印语句就打印toString的内容
equals(): Object 的equals方法是比较的地址。要想让自定义的类的对象比较内容,那么就必须重写equals 方法 来比较内容
Object
public static String toString(对象) 返回参数中对象的字符串表示形式
public static String toStirng(对象,默认字符串) 返回参数中对象的表示形式,如果对象为空,那么就以默认的字符串表示
public static Boolean isNull(对象) 判断对象是否为空
public static Boolean nonNull (对象) 判断对象是都不为空
Objects:是Object类的工具类 里面的方法都是静态方法
BigDecimal(double val) 参数为double
BigDecimal(Stirng val) 参数为String
构造方法:
public BigDecimal add(另一个BigDecimal对象) 加法
public BigDecimal subtract(另一个BigDecimal对象) 减法
public BigDecimal multiply(另一个BigDecimal对象) 乘法
public BigDecimal divide(另一个BigDecimal对象) 除法
常用方法:
public BigDecimal divide(另一个BigDecimal对象 , 精确几位 ,舍入模式) 除法
特殊方法:
BigDecimal:精确操控小数的类
包装类是将基本类型数据封装成对象 用于基本类型与字符串之间的转换!
包装类概述:
byte --> Byte
short --> Short
int --> Integer
long --> Long
float --> Float
double --> Double
char --> Character
boolean --> Boolean
包装类分类:
子主题 1
Integer类构造方法:
子主题 4
子主题 5
包装类:
public static String toString(int[] a) 返回指定数组的内容的字符串表示形式
public static void sort(int[] a)\t 按照数字顺序排列指定的数组
Arrays.asList(arr) 生成的集合 不能增删 只能改查
Arrays:是Array的工具类
Date date = new Date(); //代表当前时间
Date date = new Date(100L) //代表的是原点时间+传递的毫秒值 所代表的时间
public long getTime() 获取时间对象的毫秒值
public void setTime(long time) 设置时间,传递毫秒值
成员方法:让Date 对象 和 基本类型的long的转换
Date(☆☆☆☆☆)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public SimpleDateFormat() 构造一个SimpleDateFormat 使用默认的格式
public SimpleDateFormat(String patterm) 构造一个SimpleDateFormat 使用指定的格式
public final String format(Date date) 将日期格式转化为日期/时间字符串
public Date parse(Stirng source) 从给定的字符串开始解析文本以生成日期
按照范围分:
Date --> String : 将没有格式 的 时间 转换为一个有时间格式表示时间的字符串
String --> Date : 把有合适的字符串转化为一个计算机可以识别的Date日期对象
按照功能分:
SimpleDateFormat(☆☆☆☆☆):
API(核心类库)
boolean add(E e) 添加元素
boolean remove(E e) 从集合中移除指定的元素
boolean removeif(Prodicate o) 根据条件进行删除
void clear() 清空集合
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 返回集合的长度
Collection的共性方法:
public static<T> void shuffle(List<T> list) 将传入的集合中的元素打乱顺序
Collections:Collection类的工具类
E remove(int index); 删除指定索引的元素,并返回该元素
ArrayList<String> al = new ArrayList<>(); al.add("abc"); for (int i =0;i<al.size() ;i++ ){ String s = al.get(i); System.out.println(s); }
E get(int index ) 返回指定索引上的元素
public Object[] toArray() 哪个集合调用 就将哪个集合转化为Object类型的数组
public <T> T[] toArray(T[] t); 哪个集合调用 就根据集合的类型转换为集合类型的数组
List集合特有的方法:
ArrayList添加第一个元素执行流程
ArrayList添加第二个元素的执行流程
添加第11个元素的执行流程
注:ArrayList底层数组初始长度是10 每次扩容1.5倍
ArrayList底层源码
ArrayList的底层是数组结构
public void addFirst(E e) 在该列表的开头插入指定元素
public void addLast(E e) 将指定的元素追加到该列表的末尾处
public E getFirst() 返回该列表中的第一个元素
public E getLast() 返回该列表的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
LindedLIst特有方法;
LinkedList由于是双向链表结构, 所以LinkedList并没有索引,因此 LinkedList底层只能模拟索引进行操作数据
public E get(int index) { int count = 0; //计数器 int size0 = this.size(); // size0 记录了 当前LinkedList集合有多少个元素。 if (index < size0 / 2) { // 从前往后 while (true) { //从前往后找的代码 //每找一次 count++; if (count == index) { //找到了 return 那个值; } } } else { while (true) { // 从后往前找 //每找一次 size0--; if (size0 == index) { //找到了 return 那个值; } } } }
底层分析:
1、LinkedList 底层是用的双向链表结构
2、LinkedList 存储进去的元素, 它把元素封装为了Node对象 (Node对象中封装了三个数值 上一个元素的地址值 当前值 下一个元素的地址值)
LinkedList<String> ll = new LinkedList<>(); ll.add("a"); ll.add("b"); ll.add("c"); String str = ll.get(1); // 这岂不是也能通过索引获取元素吗? // 为什么LinkedList 提供了一个get方法呢。 因为他实现了List接口 , 而List接口中有一个get方法。 // 但是 LinkedList 的get方法底层 绝对不是通过索引拿的, // 而是先去判断 你拿的位置是属于 链表的前半部分 还是后半部分。 // 如果属于前半部分 就会从第一个往后找, 如果属于后半部分 就会从最后一个往前找。
疑问:LinkedList中并没有索引 但是却有一个get(int index )方法 而它的参数却是需要一个int类型的索引 两者岂不是矛盾?
3、LinkedList 只能从头往后 或则从后往前查询 但它却提供了一个get(int index)方法
源码分析:
LinkedList
List接口
Set数组特点: 不能存储重复元素,没有索引 存取无序
在TreeSet集合中想要存储自定义对象的类型 因为TreeSet集合有按照规则排序的特点 则需要在自定义类中实现 Comparable 接口 并重写抽象方法 compareTo方法 并添加相应的规则
返回值: 0 表示重复,表示当前添加的元素是重复的,不添加。 正数 : 往右添加 负数: 往左添加
自然排序:
在TreeSet集合使用自定义的对象 如果这个自定义对象不具备(没有实现 Comparable 接口 ) 那么就需要对其提供一个比较规则 叫做比较器 Comparator 里面有一个 compare 方法 方法中添加规则
第一个参数 是即将添加的元素, 第二个参数 是已经添加过的元素
比较器排序:
注:比较器排序优先于自然排序
TreeSet排序方式
注: TreeSet底层是红黑树结构
TreeSet:去重复、没有索引
1、HashCode()方法 的返回值 就是哈希值
2、HashCode()方法是Object的 所有的对象都可以调用HashCode方法
3、HashCode方法的底层调用的是一个本地函数,通过对象的地址值和算法得到唯一的数字,不同的对象这个 哈希值绝对是不同的
4、类继承了Object 也是可以重写Hash Code方法的 方法重写之后的返回值 就不再代表地址了
哈希值:
如果判定对象的地址值相同就是一个对象 那么就不能重写equals方法 因为equals方法默认比较的是地址值 底层就是一个 ==
如果判断属性相同的对象是一个元素 那么就需要重写equals方法 和hashCode 方法
什么时候重写hashCode方法和equals方法:
首先 hashSet拿着元素的hashCode的值 和hash表 中所有的元素进行比较 如果不存在 那么就将该元素直接添加到哈希表中 如果存在 那么就然这个元素用equals方法和哈希表中相同的元素进行一一的比较 如果都是false 则添加到集合中 如果有返回为true 则不添加到集合中
HashSet去重原理:
HashSet
特点:存取有序 、去重 、无索引
底层结构: 底层在使用哈希表的基础上 额外使用了一个链表来存储 是集合存取有序
LinkedHashSet :
Set接口
List:可以存储重复元素、有索引、有序
Set:存储的元素自动去重、没有索引、无序
List接口和Set接口的区别:
单列集合:Collection接口
void remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合中是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 返回集合中的键值对个数
Set<key> keySet(); 返回一个Set集合 里面存储了map集合中的key
value get(key); 获取map集合中键值对应的value值
Collection<value> values() 返回collection 集合 包含了map集合中value的值
Map方法 :
Set<String> keySet = map.keySet(); for (String s : keySet) { Integer value = map.get(s); System.out.println(s+"->"+value); }
1、通过键找值:
2、根据键值找键和值:
3、forEach:
Map集合的遍历方式:
Map 集合: 要求键 不重复的 底层必须要去重
HashMap : 利用的哈希表结构 给键 去重
1、如果 我们认为每 new 一个自定义类对象 就是一个不同的对象 就不能去重 不需要重写equals 和 HashCode方法
2、如果我们认为自定义类对象 的属性 如果相同了 就认为 是同一对象 需要去掉重复 就需要重写equals 和 HashCode方法
如果我们想要使用HashMap在键位置上 存储自定义类对象
HashSet 的底层 是使用 HashMap 的键来完成的 值 直接为null
HashMap
Map 集合:要求键与值 不重复 底层必须要去重
TreeMap :利用的红黑树结构 给键 去重的
1、如果自定义类什么都不继承 什么都不实现 直接让TreeMap的键位置存储 那么将会运行报错 因为TreeMap 的键 是红黑树结构 是需要排序的 然而自定义类并没有提供排序规则 所以存储添加的时候报错
1、让自定义类 实现Comparable 接口 重写CompareTo方法 一个参数 this - 参数 :升序
2、创建TreeMap的时候在构造方法的参数上传递一个Comparator 的子类对象Compare 两个参数 o1-o2升序
2、正是因为 TreeMap 的键是红黑树结构的 所以想要使用自定义类必须提供排序规则
如果我们想要使用TreeMap在键的位置存储自定义的类对象
TreeSet 的 底层就是受用的TreeMap的键来完成的 值的位置直接设置为null
TreeMap
Map的子类:
键和值的关系 就像是 身份证上的身份证号 和姓名 身份证号 有且只有相对应的人 而姓名却可以有多个人用一个姓名
键是不能重复的 值可以重复
双列集合的特点: 键 和 值 都是一一对应的
双列集合:Map接口
/* 集合调用迭代器方法,返回迭代器接口对象 */ Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("a".equals(s)){ it.remove(); } }
单例集合通用的遍历方式出现了迭代器进行遍历
注:并不是只有单例模式可以使用迭代器,只要实现了Iterator接口的子类就可以创建迭代器
迭代器:
注:增强for是为了简化迭代器二出现的,底层就是使用的迭代器
for (元素类型 变量名 : 集合) { //语句体 }
格式:
public class Demo02 { public static void main(String[] args) { MyArrayList ma = new MyArrayList(); ma.add("abc0"); ma.add("abc1"); ma.add("abc2"); ma.add("abc3"); ma.add("abc4"); ma.add("abc5"); ma.add("abc6"); ma.add("abc7"); ma.add("abc8"); ma.add("abc9"); ma.add("abc10"); for (String s : ma) { System.out.println(s); } }}class MyArrayList implements Iterable<String> { private String[] strs = new String[10]; private int index = 0; public void add(String s) { if (index >= strs.length) { throw new RuntimeException("对不起 咱们自己定义的集合 只能存10个长度 你再存多了 存不进去哦"); } strs[index++] = s; } @Override public Iterator<String> iterator() { return new Iterator<String>() { int count = 0; @Override public boolean hasNext() { if (count >= index) { return false; } return true; } @Override public String next() { return strs[count++]; } }; }}
增强for的目标:什么内容可以使用增强for循环遍历呢? 所有实现了Iterable接口的 类的 对象都可以被增强for循环遍历
增强for中是不会出现索引的 因为底层是使用的迭代器,然而 迭代器没有索引
普通for中 是由索引的
增强for与普通for的比较:
增强for:
迭代器与增强for
Object[] toArray () 以正确的顺序(从第一个到最后一个) 返回到包含所有元素的数组中
<T> T[] toArray(<T> t) 返回一个包含所有元素的数组 该数组的类型是指定数组的运行时类型
集合转数组:
Arrays --> static <T> List<T> asList(T... a) 生成的集合 不能增删 只能改查
List.of(arr) 生成的集合不能增删改 可以查
数组转集合:
注:数组与集合之间的转化:
集合
集合总结:
Stream流介绍: Stream使用来优化操作集合代码的
Stream流的原理 : Stream是基于匿名内部类的延迟执行现象
default Stream<E> stream()
1、collection体系的集合可以使用默认方法 stream()生成Stream流
2、Map体系的集合间接生成流
3、数组可以通过Stream接口的静态方法of(T ... values)生成流
生成流方法:
Predicate接口中的方法\tboolean test(T t): 对给定的参数进行判断,返回一个布尔值\t
Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
Stream<T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
常见的创建中间方法
Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流 去重
Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序
Stream<T> sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
Function接口中的方法\t\tR apply(T t)
<R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
IntStream:表示原始 int 流
ToIntFunction接口中的方法\t int applyAsInt(T value)
IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
中间方法
中间操作方法:
Consumer接口中的方法\tvoid accept(T t): 对给定的参数执行此操作
void forEach(Consumer action):对此流的每个元素执行操作
long count():返回此流中的元素数
终结方法:
Stream流的三类方法:
它是通过工具类Collectors提供了具体的收集方式 collect方法只能获取到流中的剩余的每一个数据 在底层并不能创建容器 也不能把数据添加到容器当中
public static <T> Collector toList():把元素收集到List集合中
public static <T> Collector toSet():把元素收集到Set集合中
Stream流收集方法:R collect(Collector collector)
Stream流:
File代表文件或则文件夹
File创建对象的时候只需要将File代表的文件或者文件见的路径传入构造方法当中 这样创建出来的File对象就知道代表的是哪个文件或文件夹了 如果 创建File对象的时候没有传参数 那么将会报错 因为File没有无参构造 所有的构造方法都是有参构造!
File的概述:
//new File(String pathname) 给定指定的路径的字符串 创建file对象 File file = new File("D:\\\\JavaItheima\\\\Java\\\\JavaSe\\\\JavaSe进阶\\\\day11_Stream流&File\\\\资料"); System.out.println(file);
File(String pathname) 将一个字符串类型的路径名 转换为抽象路径名 来创建新的File对象
File的构造方法:
public boolean createNewFile() 创建文件 如果父目录不存在则报错。 创建成功返回true
public boolean mkdir() 创建文件夹 如果父目录不存在不会报错 但是会创建失败。 创建成功返回true
public boolean mkdirs() 创建文件夹 如果父目录不存在则会连通父目录一起创建出来 创建成功返回true
public boolean delete() 删除文件或者文件夹 , 删除东西不走回收站的, 如果删除的是文件夹 那么必须是空文件夹才可以删除。
public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
public boolean isFile() 测试此抽象路径名表示的File是否为文件
public boolean exists() 测试此抽象路径名表示的File是否存在
1、如果调用者是文件 那么获取的是文件名和后缀名
2、如果调用者是一个文件夹 那么获取的是文件夹的名字
注:
public String getName() 返回由此抽象路径名表示的文件或目录的名称
1、当调用者是一个路径不存在真实的File对象时 返回的数组为null
2、当调用者是 一个文件时 返回的数组是null
3、 当调用者是一个空文件夹是 返回的数组是一个长度为0 的数组
4、 当调用者是一个有内容的文件夹是 正常返回
5、当调用者是要给需要权限才能进入的文件夹时 但是你的jvm却没有足够的权限 则返回的数组是null
注:进入文件夹 获取这个文件夹里面所有的文件和文件夹的File对象 并把这些File对象放在一个数组的返回
隐藏文件和隐藏文件夹都可以获取
File[] listFiles = src.listFiles();
public File[] listFiles() 返回由此文件夹下第一层所有的文件和文件夹
File的成员方法:
/:单纯的路径符号
\\:这个符号的含义为转义 转义当前符号的后一位 想要使用当前路径需要进行再次转换! \\\\
注:路径中的符号问题
File:
File file = new File("D:\\\\JavaItheima\\\\Java\\\\JavaSe\\\\JavaSe进阶\\\\day11_Stream流&File\\\\资料");
绝对路径是以盘符开始的路径
什么是绝对路径?
绝对路径:
File f = new File("../a.txt");
相对路径在Java中是基于当前项目的路径 在当前项目下开始
什么是相对路径?
相对路径:
相对路径与绝对路径:
input输入流:从硬盘中读取到内存中
output输出流:从内存中写入到硬盘中
按照流向:
字节流:可以搬运任意的文件类型 并且是原封不动的搬运每一个字节 效率最高
字符流:字符流不是用来搬运的 也不是用来复制文件的 只是用来读取文字 或写出文字的
按照功能:
I/O流的分类:
注:字节输出流传输时字节时字节是原封不动的传输 怎么获取的怎么传输 到文件中 但是 在运行结束后 打开文件看到的可看懂的都是经过软件的解码的文件,字节流并没有参与解码!
public class Demo01 { public static void main(String[] args) throws IOException { //创建字节输出流: FileOutputStream outputStream = new FileOutputStream("E:\\\\test\\\\a.txt"); //写数据 outputStream.write(97); //释放资源 outputStream.close(); }}
字节输出流演示:
1、字节输出流创建对象的时候 如果文件存在 则清空 如果不存在则创建一个新的
2、追加写入需要在构造方法 中添加true 默认位false
outputStream.write("abc".getBytes());
3、如果写入一个字符串进行输出 可以先把字符串转换位字节数组 <T>.getBytes()
\\ : z转义
\\\\ : 取消转义 用来表示目录结构
\\t: table 键
\:return 返回 -- 把光标返回到行首位置
\:newLine 新一行
4、写出换行
5、流对象使用完成后需要调用close方法进行关闭 流对象是内存连接硬盘的通道 这种流通道如果不手动关闭 垃圾回收器是不会进行回收的
finally概述: finally是为了在任何的终端逻辑中 都能够确保close方法的执行
finally 关键字 必须要和 try catch一起使用
finally 关键字 不可以单独使用
finally注意事项:
try { } catch (IOException e) { e.printStackTrace(); }finally { //释放资源 outputStream.close(); }
finally使用格式:
try {\tFileOutputStream fos = new FileOutputStream("D:\\\\a.txt");\tfos.write(97);} catch (IOException e) {\te.printStackTrace();} finally{\tfos.close(); //编译报错 fos 只在try的大括号内有效。}
第一版:编译报错 局部变量超出作用范围
FileOutputStream fos;try {\tfos = new FileOutputStream("D:\\\\a.txt");\tfos.write(97);} catch (IOException e) {\te.printStackTrace();} finally{\tfos.close(); //编译报错 fos 是局部变量 局部变量使用之前必须先赋值。 然而 如果 fos的复制出现异常了 fos就没有值了。}
第二版:编译异常 局部变量使用之前必须先赋值
FileOutputStream fos = null;try {\tfos = new FileOutputStream("D:\\\\a.txt");\tfos.write(97);} catch (IOException e) {\te.printStackTrace();} finally{\tfos.close(); //编译报错 因为 close方法 有一个编译期异常 所以报红线}
第三版:编译异常 close有异常
FileOutputStream fos = null;try {\tfos = new FileOutputStream("D:\\\\a.txt");\tfos.write(97);} catch (IOException e) {\te.printStackTrace();} finally{\ttry{\t\tfos.close(); // 可能有运行时的NullPointerException, 因为 fos初始值是null 如果 fos创建对象不成功,那么fos就是null null调用close方法就报空指针异常\t}catch (Exception e){\t\te.printStackTrace();\t}\t}
第四版:运行异常 fos对象可能出现null指针异常
演化:
FileOutputStream fos = null;try {\tfos = new FileOutputStream("D:\\\\a.txt");\tfos.write(97);} catch (IOException e) {\te.printStackTrace();} finally{\tif (fos !=null){\t\ttry{\t\t\tfos.close(); \t\t}catch (Exception e){\t\t\te.printStackTrace();\t\t}\t\t}}
最终使用:
try (FileOutputStream outputStream = new FileOutputStream("E:\\\\test\\\\a.txt");) { outputStream.write("abc".getBytes()); }catch (IOException e){ e.printStackTrace(); }
jdk1.7之后出现了新的io流异常处理方法 为了简化书写的
使用finally把close代码包裹:字节输出流异常处理
finally:确保close方法的执行
字节输出流:
1、如果要输入的文件路径不存在 那么将会报错
2、字节输入流 每次读取的字节 的返回值就是读到的那个字节数据 也就数字符在码表中对应的那个数字
3、如果我们想看的是字符数据 那么可以转化为char类型
public class Demo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("./day12_字节流/byteTest/a.txt"); int read = fis.read(); fis.close(); System.out.println((char) read); }}
基本格式:
public class Demo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("./day12_字节流/byteTest/a.txt"); int b; while ((b = fis.read()) != -1) { System.out.print((char) b + " "); } fis.close(); }}
循环每次一个字节读取:
循环读取每个字节数组:
循环读取每个字节:
字节输入流:
public class Demo05 { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("E:/test/a.txt"); FileOutputStream fos = new FileOutputStream("E:/a.txt"); int result ; while ((result =fis.read())!= -1){ fos.write(result); } fos.close(); fis.close(); }}
文件复制:一次读取一个字符
文件复制:一次读取一个字节数组
经典案例:
注:缓冲流只提供缓冲的作用 并不实际去操作数据
BufferedInputStream
BufferedOutputStream
构造方法 必须传入一个 具体能够操作文件的流
public class Demo01 { public static void main(String[] args) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/a.txt")); bos.write(98); bos.close(); }}
字节缓冲流的原理: 底层提供了一个 1024 * 8 长度的字节数组 看是是一次读写一个字节 其实只是把字节存储到了字节数组中 等到数组满了之后在一块写入到文件中
字节缓冲流:
字节流:
字节流可以复制、传输任意的文件 因为字节流是把文件原原本本的复制到另一个文件中 编辑器在打开文件的时候自动编码进行展示
但是 在把文本中的文字去读到程序中 对其进行操作时 字节流就有可能做不到 因为 文字 比如说 中文 在不同的编码环境中 里面的编码字节数量就不同 如 GBK环境下 2个字节转换为 1个字符 但是在UTF-8环境下 却是3个字节转换为一个字符! 所以我们就迫切的需要一个可以专门操作字符的流 对文本进行操作!!
为什么要学习字符流?
字节 + 编码表 = 字符!
字符流的底层:
编码:编码是字符转换为计算机存储经过的编码规范
解码:解码是将计算机存储的二进制数据转换为我们能看得懂的字符 是经过编码表的查询转换的过程
乱码 :乱码造成的原因是没有编码与解码的格式不一致
编码、解码及乱码
字符流:
I/O流:
多个任务 并列执行(同时执行)
并行:
并发:
每个应用就是一个进程
进程:
一个应用中 存在多条执行的路径 每个路径就是一个线程 线程是cpu的最小执行单位
cpu在同一时刻只能执行一条线程 而我们看到的多线程同时执行 其实是 cpu在多线程之间 做着告诉的切换 以至于我们认为是在同时执行
多线程的执行原理:
Java中 多线程的执行方式 是抢占式 调度 (随机的)
调度方式:
线程:
多线程的概念
class MyThead extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("新线程" + i); } }}
public class Demo01 { public static void main(String[] args) { MyThead mt = new MyThead(); mt.start(); for (int i = 0; i < 100; i++) { System.out.println("run后"+i); } }}
继承Thread类:
class MyRunnable implements Runnable{ @Override public void run() { System.out.println("Runnable接口"); }}
public class Demo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread thread = new Thread(mr); thread.start(); }}
实现Runnable接口:
class MyCallable implements Callable<String>{ @Override public String call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println("表白女孩"); } return "答应"; }}
实现Callable<>接口:
多线程的实现方式:
设置线程名.setName()
获取线程名.getName()
设置和获取线程名:
Thread.currentThread().getName()
获取当前线程
优先级: 1 - 10 默认值:5
.setPriority();
设置线程的优先级
t2.setDaemon(true);
设置守护线程
线程的方法:
1、多个线程操作共享数据
2、多个线程操作共享数据的代码 是分开的 线程可以在执行这些分开的代码之间被其他线程抢掉
引起线程安全问题的原因:
同步代码块:用synchronized关键字包起来的代码。就叫做同步代码块。
synchronized (任意锁对象){}
方案:
线程数据安全问题:
概念:如果多个线程在同步代码块上使用的锁对象是 同一个 我们就说这几个线程是同步的
假设 在同步代码块中 使用的同一把锁 obj两个线程对象为 : t1 t2
synchronized(obj){\t代码1 //如果t1线程在代码1这个位置睡眠了, 那么t2 是不能进入代码2执行的。}synchronized(obj){\t代码2 // t2进不来。}
例:
线程同步:
非静态的同步方法的锁对象 是this
静态的同步方法的锁对象 是 当前方法所在的类的class文件对象
class Student {\tpublic void show1(){\t\tsynchronized(this){\t\t\t//n行代码 1\t\t}\t}\tpublic synchronized void show2(){ //show1 和show2 是同步的\t\t//n行代码 1\t}\tpublic static void method1(){\t\tsynchronized(Student.class){\t\t\t//n行代码 2\t\t}\t}\tpublic static synchronized void method2(){ // method1 和method2是同步的\t\t\t\t\t//n行代码 2\t}}
同步方法(重点):
在jdk1.5的时候 出现了一个类 Lock 接口 用于让多个线程实现同步
public class SellTicket implements Runnable {\tprivate int tickets = 100;\t//private Object obj = new Object();\tprivate Lock lock = new ReentrantLock(); // 如果想让多个线程 同步。 必须是多个线程用同一个lock\t@Override\tpublic void run() {\t\twhile (true) {\t\t\t\ttry{\t\t\t\t//synchronized (obj) {\t\t\t\tlock.lock();\t\t\t\t\tif (tickets > 0) {\t\t\t\t\t\ttry {\t\t\t\t\t\t\tThread.sleep(100);\t\t\t\t\t\t} catch (InterruptedException e) {\t\t\t\t\t\t\te.printStackTrace();\t\t\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\tSystem.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");\t\t\t\t\t\ttickets--; \t\t\t\t\t}\t\t\t\t//}\t\t\t}finally{\t\t\t\tlock.unlock();\t\t\t\t\t\t\t\t\t}\t\t\t\t\t\t}\t}}
Lock接口:
锁的嵌套 容易出现死锁的情况 开发中应当尽量避免锁的嵌套
Object objA = new Object();Object objB = new Object();new Thread(()->{\twhile(true){\t\tsynchronized (objA){\t\t\tSystem.out.println("线程1A"); //第一步: 线程1 执行到此处 被线程2抢到了cpu 此时 objA已经被锁上了。\t\t\tsynchronized (objB){\t\t\t//第三步:线程1抢到cpu之后 进不去了因为 objB已经被线程2锁上了。只好让出cpu给线程2\t\t\t\tSystem.out.println("线程1B");\t\t\t}\t\t}\t}}).start();new Thread(()->{\twhile(true){\t\tsynchronized (objB){\t\t\tSystem.out.println("线程2B"); // 第二步: 线程2 进来之后 执行到此处 被线程1抢到了cpu 此时objB被锁上了。\t\t\tsynchronized (objA){\t\t // 第四步:线程2 拿到cpu之后 进不去了因为 objA已经被线程1锁上了。 也只能把cpu让给线程1......就这样让来让去 这就是死锁。\t\t\t\tSystem.out.println("线程2A");\t\t\t}\t\t}\t}}).start();
死锁案例:
死锁:
public class Cooker extends Thread {\t// 生产者步骤:\t// 1,判断桌子上是否有汉堡包\t// 如果有就等待,如果没有才生产。\t// 2,把汉堡包放在桌子上。\t// 3,叫醒等待的消费者开吃。\t@Override\tpublic void run() {\t\twhile(true){\t\t\tsynchronized (Desk.lock){\t\t\t\tif(Desk.count == 0){\t\t\t\t\tbreak;\t\t\t\t}else{\t\t\t\t\tif(!Desk.flag){\t\t\t\t\t\t//生产\t\t\t\t\t\tSystem.out.println("厨师正在生产汉堡包");\t\t\t\t\t\tDesk.flag = true;\t\t\t\t\t\tDesk.lock.notifyAll();\t\t\t\t\t}else{\t\t\t\t\t\ttry {\t\t\t\t\t\t\tDesk.lock.wait(); //释放锁\t\t\t\t\t\t} catch (InterruptedException e) {\t\t\t\t\t\t\te.printStackTrace();\t\t\t\t\t\t}\t\t\t\t\t}\t\t\t\t}\t\t\t}\t\t}\t}}
public class Demo {\tpublic static void main(String[] args) {\t\t/*消费者步骤:\t\t1,判断桌子上是否有汉堡包。\t\t2,如果没有就等待。\t\t3,如果有就开吃\t\t4,吃完之后,桌子上的汉堡包就没有了\t\t\t\t叫醒等待的生产者继续生产\t\t汉堡包的总数量减一*/\t\t/*生产者步骤:\t\t1,判断桌子上是否有汉堡包\t\t如果有就等待,如果没有才生产。\t\t2,把汉堡包放在桌子上。\t\t3,叫醒等待的消费者开吃。*/\t\tFoodie f = new Foodie();\t\tCooker c = new Cooker();\t\tf.start();\t\tc.start();\t}}
生产者和消费者模式:
wait():等待之后 会把锁打开
sleep():抱着锁睡觉
wait和sleep的区别:
锁对象是任意对象 任意对象为什么可以调用wait和notify?因此 wait和notify是定在object中
wait和notify方法 必须 使用锁对象来调用
wait和notify方法 必须在同步代码块中被调用 在其他地方调用就报错
wait:是唤醒锁对象上等待的线程中随机的一个 如果没有等待的线程则自动忽略
notify:是唤醒锁对象上所有等待的线程 如果没有等待的线程 则自动忽略
wait()和notify()
线程通信总结:
线程间通信问题:
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);abq.put("汉堡包");System.out.println("执行1");/*abq.put("汉堡包2"); //底层会调用wait方法System.out.println("执行2"); //无法执行*/System.out.println(abq.take());System.out.println("执行2");System.out.println(abq.take());//底层会调用wait方法System.out.println("执行3"); // 无法执行
使用方式:
public class MyArrayBlockingQueue<E> { private int capacity; private ArrayList<E> list = new ArrayList<>(); private int count = 0; public MyArrayBlockingQueue(int capacity) { this.capacity = capacity; } public synchronized void put(E e) throws InterruptedException { if (count == capacity) { this.wait(); } list.add(e); count++; this.notifyAll(); } public synchronized E take() throws InterruptedException { if (count == 0) { this.wait(); } this.notifyAll(); return list.remove(count - 1); }}
源码解析:
//桌子ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);new Thread(){ //吃货\t@Override\tpublic void run() {\t\twhile(true){\t\t\ttry {\t\t\t\tSystem.out.println("吃货吃了一个 :" + abq.take());\t\t\t} catch (InterruptedException e) {\t\t\t\te.printStackTrace();\t\t\t}\t\t}\t}}.start();new Thread(){\t@Override\tpublic void run() {\t\twhile(true) {\t\t\ttry {\t\t\t\tSystem.out.println("厨师做了一个汉堡包-------------------------------:汉堡包");\t\t\t\tabq.put("汉堡包");\t\t\t} catch (InterruptedException e) {\t\t\t\te.printStackTrace();\t\t\t}\t\t}\t}}.start();
生产者与消费者模式:
阻塞队列:
Thread.State.TIMED_WAITING :线程状态
NEW :尚未启动的线程处于此状态。
RUNNABLE :在Java虚拟机中执行的线程处于此状态。
BLOCKED :被阻塞等待监视器锁定的线程处于此状态。
WAITING :正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED :已退出的线程处于此状态。
线程的状态:
创建一个无线大小的线程池(最大不超过int的范围):
public class Demo { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+"执行了"); }); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+"执行了"); }); executorService.submit(()->{ System.out.println(Thread.currentThread().getName()+"执行了"); }); executorService.shutdown(); }}
创建一个指定大小(指定最大范围)的线程池
案例:
参数一:核心线程数量
参数二:最大线程数
参数三:空闲线程最大存活时间
参数四:时间单位
参数五:任务队列
参数六:创建线程工厂
ThreadPoolExecutor.AbortPolicy: \t丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: \t丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
参数七:任务的拒绝策略:
线程池的参数:
使用ThreadPoolExecutor来创建线程池:
线程池:
public class Demo { public static void main(String[] args) { Volatile vo = new Volatile(); vo.start(); while (true){ if (Volatile.a == 1){ System.out.println("主线程读取到了a=1"); break; } } }}class Volatile extends Thread{ public static volatile int a = 0; @Override public void run() { System.out.println("线程启动了 , 休息两秒"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("将a的值修改为1"); a = 1; System.out.println("程序结束"); }}
多线程的可见性--volatile 解决
原子类底层使用的是乐观锁,原子类的乐观锁只是锁住核心代码对数据进行重复检验 但是在原子类外的裸漏数据并没有进行同步检验
public class Demo { public static void main(String[] args) throws InterruptedException { atomicity a1 = new atomicity(); atomicity a2 = new atomicity(); a1.start(); a2.start(); Thread.sleep(1000); System.out.println("atomicity-->"+atomicity.a); }}class atomicity extends Thread{ public static AtomicInteger a = new AtomicInteger(); @Override public void run() { for (int i = 0; i < 100; i++) { a.getAndIncrement(); } System.out.println("修改完毕"); }}
多线程的原子性---原子类解决(底层采用的乐观锁机制)
子主题 3
线程安全问题:
理解: 乐观锁是使用悲观锁将小段的核心逻辑代码实现同步锁 对其进行重复判断 如果正确则停止 判断 并实现自增 如果错误则得到一个true的结果再次进行判断 由于乐观锁只是锁主的小段的核心逻辑并且对其重复的判断 效率相较于悲观锁 较高!因此称为乐观锁
乐观锁:
理解:悲观锁是使用synchronized 关键字以及 lock锁进行 对代码实现同步 由于锁住了大段的逻辑 每次的调用只有等待上一个执行完才能再次进行抢夺cpu执行权 因此造成了效率的大幅度减低 因此被称为悲观锁!
悲观锁:
针对 大段的逻辑 上下文关联的。 要把大段的代码 变成 原子性的。
针对 多改 少查 用悲观锁。
悲观锁(synchronized):
乐观锁只是对值的修改 并 对其加以校验 具体的执行逻辑并不管--》 乐观锁只是实现核心逻辑的同步
乐观锁的使用针对于对查询多 修改少的地方
悲观锁是将大段的逻辑实现同步 变成原子性的 是有上下文关联的同步代码块
悲观锁一般使用在修改多 查询少的地方
乐观锁与悲观锁的区别:
悲观锁与乐观锁:
多线程
双列集合:
CountDownLatch的原理:
Semaphore:
并发工具类:
网络编程
类加载器 是将 .class 文件 使用io流 将数据读取到方法区中 加载器同时还会将读取到的字节 整合成一个 类模板
类加载器的作用:
JVM开启之后 如果没有对这个类进行调用 那么JVM就不会加载这个类的 .class文件 并且 只有在第一次使用这个类的时候 jvm才会对其进行加载 后续的使用中 并不会再加载!而 方法区中的东西 一旦读入 是不会被垃圾回收器进行回收 知道jvm关闭静态区 也是在方法去中 也是第一次使用才会加载 不适用不会加载 后续使用也不会加载
加载时机:
类加载的过程是通过包名和类名 确定这个类的 class文件的路径 然后再将 .class文件读取到内存中 形成一个类模板 并且创建一个 Class类的对象 来表示这个类模板!
加载:
检查class文件内容是否符合规范,是否安全,是否是木马等攻击性文件
验证:
给静态变量分配空间 并对空间赋值默认值
class Student {\tstatic int a=10; // 比如a在准备阶段的时候 有了存储空间 而且这个空间用0来占位 但是这个10 的赋值是在初始化阶段完成的}
准备:
如果类中涉及到其他类 类型的 符号引用替换为直接引用
class Student {\tString name; // 比如 Student中使用了String 在把Student加载进入内存的时候 要在解析阶段 和String的class文件进行关联。}
解析:
连接:
给静态变量进行赋值
class Student {\tstatic int a=10; // 比如a在准备阶段的时候赋值为了0 但是在初始化阶段 就把0替换成了10}
初始化:
加载过程:
类加载器的基本概念:
系统类加载器:BootStrapClassLoader
平台类加载器:PlatformClassLoader
应用程序类加载器:AppClassLoader
自定义类加载器
类加载器的分类:
程序定义类的目的是在jvm中使用它,那么为什么要划分出3中类加载器,如果直接设计为一个类加载器不是更加方便吗?其实这是为了系统安全,而使用的双亲委派机制,双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,\t如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,\t倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,\t即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢??\t采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。\t其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,\t而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,\t而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。\t可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?\t该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,\t将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。\t但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常java.lang.SecurityException: Prohibited package name: java.lang
双亲委派机制:
作用:读取网络中传输的class文件 并对其创建对象
自定义类加载器:
public class Demo {\tpublic static void main(String[] args)throws Exception {\t\t/* \t\tClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); // 系统类加载器 是专门 加载 自己写的类的\t\t//ClassLoader classLoader = Demo.class.getClassLoader();\t\tInputStream is = systemClassLoader.getResourceAsStream("a.properties");\t\t*/\t\t//FileInputStream is = new FileInputStream("advance33/src/a.properties");\t\t/*\t\t源码 : java文件\t\t字节码文件: class文件\t\t今后你给用户的是class文件 , 一般不给java文件 \t\t而给的class文件 那个模块里面 以后是没有src的 \t\t */\t\t//FileInputStream is = new FileInputStream("out/production/advance33/a.properties");\t\t/*\t\t今后你是给他 advance33(模块名)呢 还是给他basic25(项目名)呢? 你给他 advance33\t\t所以今后就没有out/production了\t\t另外用户可能还会把 advance33 改名字。\t\t所以 这个地方 就算你用的是相对路径 你也不能写死 out 写死 production 写死 advance33\t\t */\t\tClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); // 系统类加载器是专门加载自己写的类的 ,所以他底层可以动态的知道 自己写的类的class文件目录的\t\tInputStream is = systemClassLoader.getResourceAsStream("day16/a.properties");\t\tProperties p = new Properties();\t\tp.load(is);\t\tis.close();\t\tSystem.out.println(p);\t}}
类加载器读取配置文件
类加载器
反射的概述:反射就是另一种创建对象 调用属性、调用方法的方式。(常用于框架中)
在框架中 底层大量的使用了反射机制
通过反射可以将框架与自己编写的代码的耦合度降到最低
框架:
降低了耦合度 运行时直接执行即可 编译器不需要同时存在!
运行过程中。动态的获取 任意的类 包括里面的 构造方法 成员变量 成员方法。
反射的特点
为什么说反射是框架的灵魂?
class文件中的成员变量 在java中 Field 类的对象去表示
class文件中的构造方法 在java中 Constructor 类的对象去表示
class文件中的成员方法 在java中 Method 类的对象去表示
class文件特点
Class aClass = Student.class;
应用场景:多线程中 静态同步方法的 锁对象 是 当前类的class文件对象。
//通过类名加载 Class className2 = Student.class; System.out.println(className2);
1、通过类获取class文件
对象.getClass
应用:equals方法
//通过对象名调用 Student stu = new Student(); Class className3 = stu.getClass(); System.out.println(className3);
2、通过对象获取class文件
Class.forName(类的全类名);
应用:框架中;最常用
//通过class文件获取 Class className1 = Class.forName("test.demo01.Student"); System.out.println(className1);
3、通过类的名字
获取方式:
获取类的class文件
.getConstructors();
System.out.println("——————————所有公共的构造方法—————————"); Constructor<?>[] constructor1 = className.getConstructors(); for (Constructor<?> constructor : constructor1) { System.out.println(constructor); }
获取所有构造方法
.getConstructor();
System.out.println("————————————无参构造——————————————");Constructor constructor2 = className.getConstructor();Student stu = (Student) constructor2.newInstance();System.out.println(stu);
获取无参构造并使用
获取有参构造并使用
.getDeclaredConstructor(String.class);
System.out.println("————————————私有构造——————————————");Constructor<?> constructor4 = className.getDeclaredConstructor(String.class);constructor4.setAccessible(true);Student stu4 = (Student) constructor4.newInstance("赵六");System.out.println(stu4);
获取私有构造并创建对象并使用
获取构造方法并使用
.getDeclaredFields();
System.out.println("—————————————类中所有的成员变量—————————————");Field[] fields = className.getDeclaredFields();for (Field field : fields) { System.out.println(field);}
获取类中所有的成员变量
.getField(属性名);
获取类中公共的成员变量并使用
.getDeclaredField(属性名);
获取类中私有的成员变量并使用
获取成员变量
返回一个包含 方法对象的数组, 方法对象反映由该 Class对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类
Method[] getMethods()
返回一个包含 方法对象的数组, 方法对象反映由 Class对象表示的类或接口的所有声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承方法
Method[] getDeclaredMethods()
System.out.println("————————————获取类中所有的成员方法——————————————");//Method[] methods = className.getMethods();Method[] methods = className.getDeclaredMethods();for (Method method : methods) { System.out.println(method);}
获取类中所有的成员方法
返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法
获取类中公共成员方法并使用
返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 Class对象
获取类中私有的成员方法并使用
获取成员方法
反射
类加载器与反射
注解:
单元测试:
注解与单元测试
注:数据结构与算法的特点: 拿空间换时间,拿时间换空间
数据在容器中的排列方式,就是数据结构
数组:查询快,增删慢
链表:查询慢,增删快
栈:先进后出
队列:先进先出
红黑树
哈希表
常见的数据结构:
数据结构
算法:
数据结构与算法:
Java基础
数据库相关概念
-- 创建数据库CREATE DATABASE 数据库名称;
# 对其进行判断在进行添加CREATE DATABASE IF NOT EXISTS 数据库名称;
# 创建一个指定字符集的数据库:CREATE DATABASE 数据库名 CHARACTER SET 字符集;
创建数据库
# 查询所有数据库创建的名称:SHOW DATABASES;
# 查询数据库创建时的设置SHOW CREATE DATABASE 库名;
-- 查看当前使用的数据库 SELECT DATABASE();
查看数据库
-- 修改数据库字符集ALTER DATABASE 库名 CHARACTER SET 修改后的字符集;
修改数据库
-- 删除数据库DROP DATABASE IF EXISTS 库名;
删除数据库
-- 使用指定的数据库USE 库名;
使用指定的数据库
操作数据库
添加表
-- 查询表结构DESC 表名;
-- 查询表创建的字符集SHOW CREATE TABLE 表名;
-- 查询当前数据库下所有的表SHOW TABLES;
查询表
#修改表名ALTER TABLE 表名 RENAME TO 新的表名;
#修改表的字符集ALTER TABLE 表名 CHARACTER SET utf8; \t
#添加列ALTER TABLE 表名 ADD 列名 数据类型;
#修改列的数据类型ALTER TABLE 表名 MODIFY 列名 新数据类型;
#修改列名和数据类型ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
#删除列ALTER TABLE 表名 DROP 列名;
修改表
# 直接删除表DROP TABLE 表名;
# 判断删除表DROP TABLE IF EXISTS 表名;
删除表
操作数据表
DDL
增
删除数据DELETE FROM 表名 [WHERE 条件] ;
-- 删除stu表中所有的数据delete from stu;
删
#将张三 的性别改为女UPDATE db_stu SET gender = '女' WHERE NAME='张三';
#注意:如果update语句没有加where条件,则会将表中所有数据全部修改!UPDATE db_stu SET gender = '女';
练习:
修改语句中如果不加条件,则将所有数据都修改!
像上面的语句中的中括号,表示在写sql语句中可以省略这部分
改
对表中的数据进行操作
DML
查询多个字段SELECT 字段列表 FROM 表名;
查询所有字段SELECT * FROM 表名;
去除重复记录SELECT DISTINCT 字段列表 FROM 表名;
起别名AS: AS 也可以省略
#查询所有数据SELECT * FROM stu;
#查询地址信息SELECT address FROM stu;
# 去重select distinct address from stu;
注:在实际开发中查询全部字段 尽量不要使用 * 进行查询 不利于阅读
基础查询
SELECT 字段列表 FROM 表名 WHERE 条件列表;
#查询条件大于20的学员信息SELECT * FROM stu WHERE age >20;
#查询年龄大于等于20岁的学员信息SELECT * FROM stu WHERE age>= 20;
#查询年龄大于等于20岁 并且 年龄 小于等于 30岁 的学员信息SELECT * FROM stu WHERE age>=20 AND age<=30;SELECT * FROM stu WHERE age>=20 && age<=30;SELECT * FROM stu WHERE age BETWEEN 20 AND 30;
#查询入学日期在'1998-09-01' 到 '1999-09-01' 之间的学员信息SELECT * FROM stu WHERE hire_date BETWEEN '1998-09-01' AND '1999-09-01';
#查询年龄等于18岁的学员信息SELECT * FROM stu WHERE age = 18;
#查询年龄不等于18岁的学员信息SELECT * FROM stu WHERE age != 18;SELECT *FROM stu WHERE age<>18;
#查询英语成绩为 null的学员信息SELECT * FROM stu WHERE english = NULL;\t\t-- 这条语句是错误的 因为null值不能进行比较使用的SELECT * FROM stu WHERE english IS NULL;SELECT * FROM stu WHERE english IS NOT NULL;
条件查询
_ : 代表了任意的单个字符 % :代表了任意的多个字符
#查询姓'马'的学员信息SELECT * FROM stu WHERE `name` LIKE '马%';
#查询第二个字是'花'的学员信息 SELECT * FROM stu WHERE `name` LIKE '_花%';
#查询名字中包含 '德' 的学员信息SELECT * FROM stu WHERE `name` LIKE '%德%';
模糊查询
#查询学生信息,按照年龄升序排列 SELECT * FROM stu ORDER BY age;\t\t-- 默认的排序就是升序
#查询学生信息,按照数学成绩降序排列SELECT * FROM stu ORDER BY math DESC;
ASC : 升序排列 (默认值)
DESC : 降序排列
排序查询
SELECT 聚合函数名(列名) FROM 表;
#统计班级一共有多少个学生SELECT COUNT(id) FROM stu;SELECT COUNT(*) FROM stu;\t-- 在统计数值的时候建议使用*来代替使用指定的字段 * 的效率比指定的字段会高
#查询数学成绩的最高分SELECT MAX(math) FROM stu;
#查询数学成绩的最低分SELECT MIN(math) FROM stu;
#查询数学成绩的总分SELECT SUM(math) FROM stu;
#查询数学成绩的平均分SELECT AVG(math) FROM stu;
null值不参与运算!!
聚合查询
SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];
* 执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
* 可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。
where是使用与分组之前的 它在select 语句中的优先级高于 group by 的优先级 \t 而having 的优先级是低于group by的优先级的\t 因此 他们的执行顺序为 先执行where 在group by进行分组 在执行having
where 和 having 的区别:
分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义
分组查询
1、起始索引是从0开始的
2、limit是mysql的方言。
3、 limit起始索引如果为0,则起始索引可以省略。
4、起始索引 = (当前页码 - 1) * 每页显示的条数
分页查询
DQL
DCL
数据库分类
MySQL
子主题 2
JavaWeb
Spring是一种轻量级IOC(控制反转)和AOP(面向切面编程)的框架
什么是Spring?
1、简化开发、降低企业级开发的复杂性
2、整合框架、高效整合其他框架,提高企业级应用开发与运行效率
Spring的好处:
Spring core:核心容器
Spring AOP:Spring面向切面编程
Spring context:Spring上下文
Spring DAO
Spring ORM:对象关系映射模块
Spring Web模块
Spring MVC
Spring模块
IOC---》控制反转,是一种由主动new产生对象转换为接收外部提供对象的一种思想
概念:
<!--构造方法方式:通过对象的无参构造实现将对象加载进ioc容器中--> <bean id="bookDao" class="com.test.setter.dao.impl.BookDaoImpl"/>
1、无参构造方法
<!--静态工厂方式:静态工厂方式将对象加载进IOC容器中后,他会随着IOC容器的创建而加载--> <bean id="staticFactory" class="com.test.factory.StaticFactory" factory-method="getBookDao"/>
2、静态工厂方法
<!--实例化工厂:与静态工厂相比,实例化工厂方法不是静态的 他是随着Bean对象的调用而加载,调用多少次就创建多少次。创建之后不会受IOC容器的管理--> <bean id="bookService" class="com.test.factory.ImplFactory"/> <bean id="implFactory" factory-bean="bookService" factory-method="getBookService"/>
3、实例工厂方法
Bean的三种实例化
<!--Bean对象的声明周期:创建与销毁--> <bean id="bookServiceImpl" class="com.test.setter.service.impl.BookServiceImpl" init-method="init" destroy-method="destroy"/>
Bean的创建与销毁
IOC(控制反转)
DI---》依赖注入,它是IOC思想的一种方式,由容器动态将某个依赖关系注入到组件中
<!--简单注入--> <bean id="bookDao" class="com.test.setter.dao.impl.BookDaoImpl"> <property name="connectionNum" value="10"/> <property name="databaseName" value="mysql"/> </bean>
简单注入
<!--引用注入--> <bean id="bookService" class="com.test.setter.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"/> <property name="userDao" ref="userDao"/> </bean>
引入注入
Setter注入
<!--简单注入--> <bean id="bookDao" class="com.test.constructor.dao.impl.BookDaoImpl"> <constructor-arg name="connectionNum" value="25"/> <constructor-arg name="databaseName" value="mysql"/> </bean>
<!--引用注入--> <bean id="bookService" class="com.test.constructor.service.impl.BookServiceImpl"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="userDao" ref="userDao"/> </bean>
构造器注入
两种注入方式:
DI(依赖注入)
自动装配
集合注入
IOC与DI的关系与区别
IOC与DI
在传统的JavaSe中 我们习惯了在对象内部通过new的方式区创建对象,是由程序主动的创建所依赖的对象。而IOC则是有一个专门来创建这些对象的容器,又称为IOC容器。因此是由IOC容器来控制对象的创建。IOC 控制了对象。控制了外部资源的获取
谁控制谁,控制什么?
在传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,这种方式及为正转。反转则是不在由我们自己去主动创建依赖的对象,而是转换为了由外部的容器来帮我们进行创建。为什么是反转? 因为由容器帮我们查找及注入依赖对象 的,对象知识被动的接收依赖的对象反转了什么? 依赖对象的获取被反转了
为什么会是反转,反转了什么?
Ioc是控制反转,它是一种设计思想想要了解什么是IOC则需要了解两个问题:
IOC大大的降低了代码的耦合。由原本的主动去new来创建出依赖对象的高耦合 转换为了由IOC容器去创建对象 将控制权交给了容器 再由容器进行注入 由主动转换为了被动 对象与对象之间 是松散耦合 程序结构更加的灵活
IOC的作用:
IOC:
对象依赖于IOC容器,因为对象需要IOC容器来提供对象所需要的外部资源
谁依赖谁,为啥要依赖?
将IOC容器注入程序中的某个对象,注入了某个对象所需要的外部资源
谁注入谁,注入了啥?
DI是依赖注入 它是实现IOC的具体方式DI的理解也需要从两方面了解
简单来说 当某个对象需要另一个对象的帮助来完成某件事时,再传统的设计中 是由我们主动的去以new的方式创建对象,现在是由Spring中的容器来完成对象的创建。应用程序再运行时依赖IOC容器来动态注入对象所需要的外部资源 称为DI
总结:
DI:
简单来说AOP就时将与业务无关但是却被业务模块通用的逻辑封装起来,来降低代码的冗余,同时也便于程序的维护
理解:
使用动态代理的设计模式再执行方法前后或异常是加入相关的逻辑
核心原理:
事务处理
权限判断
日志
常用方式:
jdk代理:
CGLB代理:
代理方式:
AOP:
Spring的两大核心及DI的作用:
Spring
SpringMVC
@SpringBootApplication注解依赖于@EnableAutoConfiguration自动配置注解@EnableAutoConfiguration注解又依赖于@Import注解 @Import注解 中导入了AutoConfigurationImportSelector类 在import类中调用了其中的selectImports方法 selectImports方法调用了getCandidateConfigurations方法 通过类加载器加载了一些文件 获取了文件都是从`META-INF/spring.factories`中获取的在`META-INF/spring.factories`文件中配置了大量的配置器的全类名,通过 String数组进行封装,返回到ioc容器中后再去初始化这些类再初始化时会根据@Conditiona注解中的条件来判断是否存在其中的jar包 因此这些存在于IOC容器中的bean是否初始化要根据导入的jar包及配置的情况来判断结论:在启动类上添加@SpringBootApplication注解就可以感知导入了那些jar包以及自动配置的功能
自动装配原理:
启动原理:
SpringBoot高级:
SpringBoot
MyBtais-pulse
Maven高级
基础框架
Java
0 条评论
回复 删除
下一页