Java基础
2022-03-24 20:27:28 0 举报
AI智能生成
史上最全Java基础+进阶知识梳理
作者其他创作
大纲/内容
Java基础
Java概述
什么是Java
Java是一门编程语言,它是由詹姆斯-高斯林1995年创造的
Java的特点
开源
面向对象
跨平台
Java的跨平台是依赖于JVM跨平台
Java开发环境
JDK
开发工具
jdk(开发环境)
jvm(运行环境)
核心类库
环境配置
Java基础语法
代码的构成
注释
单行注释 //
多行注释 /**/
文档注释 /***/
关键字
有特殊含义的单词都是关键字
标识符(所有的名字都是标识符) 注意事项:
1、必须是数字、字母、下划线、$符
2、不能是数字开头
3、不能是关键字
4个命名习惯
1、见名知意
小驼峰(变量、方法名)
大驼峰(类名、接口名)
峡谷先锋(变量名)
数据
常量
整形
浮点型
布尔型
字符型
字符串
null
变量
基本类型(四类八种)
整形
byte 1字节
short 2字节
int 4字节
long 8字节
浮点型
flaot 4字节
double 8字节
字符型
char 2字节
布尔型
Boolean(布尔类型不参与运算)
引用类型
类
接口
数组
一维数组
三种定义格式
int[] arr = new int[4];
int[] arr = new int[]{10,20,30};
int[] arr = {10,20,30};
数组的访问
数组的访问方式是通过索引进行访问的
数组的索引是从0开始的
数值的最大值位数组长度-1,超过范围将会提示数组越界
数组的内存图
1、一个数组的内存图
2、两个数组的内存图
3、两个数组指向同一个数组的内存图
数组常见的问题
1、空指针异常:使用null对其进行了赋值
2、索引越界异常:访问的数组索引是数组中没有的
数组的遍历方式
多维数组
变量的注意事项
1、使用之前必须赋值
2、变量可以连续定义
3、同一范围内不能连续定义
4、变量只在当前范围内有效
5、F/L
类型转换
1、隐式转换
比int大的自动转换为大的类型
比int小的自动转换为int类型再进行运算
2、强制转换
明确能转过去的
明确传不过去的
强行取出小数点的
类型转换注意事项:
常量优化机制
比int小的类型才有常量优化机制(注:String类型也有!)
小数类型也有数据类型(double)
long类型的数据后面为什么加L?
boolean类型不参与
char和整形的转换
AscII码表
符号
基本符号
运算符号
算术运算符
基本算术运算符
+
-
*
/
%
自增自减运算符
++
--
注意事项:
1、不论自增或自减放在变量的前面还是后面,当程序经过后都会进行运算
2、自增或自减放在变量的后面,那么i++这个表达式整体取得是没有经过运算的值
3、自增或自减放在变量的前面,那么++i这个表达式取得的是i+1之后的值
赋值运算符
基本赋值(=)
复合赋值
+=
-=
*=
/=
比较运算符
逻辑运算符
普通逻辑
短路逻辑
三元运算符
位运算
结构语句
顺序结构
选择结构
if的三种格式
if(){ }
if(){ }else{ }
if(){ }else if(){ }else if(){ }else{ }
switch
switch注意事项
1、case后面必须是常量
2、case后面不能重复判断
3、switch小括号中的数据类型必须是:byte、short、char、int、String、枚举
4、switch中的default可以省略
5、switch中的default可以放在任意位置
6、break用来结束switch的,break可以省略,但是会发生case穿透问题
switch和if的比较
1、能用switch的地方那么就能用if表示
2、switch的效率必if高, 开发中的规律:代码越麻烦,执行效率越低
循环结构
for循环
while循环
dowhile循环
while循环和for循环的比较:
1、while循环和while循环在技术上没有任何的区别
2、习惯上for循环用在有明确范围的地方,while循环使用在没有明确范围的地方上 如珠穆朗玛峰问题!
方法
方法的作用:方法可以提高全局代码的复用性
方法的使用
定义格式
没有参数,没有返回值的方法
public static void 方法名(){
语句;
}
带参数的方法
public static void 方法名(数据类型 变量名,数据类型 变量名,数据类型 变量名,.... ){
方法体;//很多条语句
}
带返回值的方法
public static 数据类型 方法名(数据类型 变量名,数据类型 变量名,数据类型 变量名,.... ){
方法体;//很多条语句
return 返回值;
}
方法定义的注意事项
1、循环也可以提高代码的复用性,但它值提高的局部代码的复用性,而方法提高了全局代码的复用性
2、方法定义之后,不调用是不会执行的,main方法才是程序的入口,只有在main方法中调用了程序才会执行
3、方法都是独立的,平级关系,不能嵌套定义
4、方法的定义并没有先后顺序
5、main方法调用另一个方法后,只有等待另一个方法执行完成后才会继续执行
方法注意事项
1、方法不能嵌套定义
2、方法与方法之间没有先后顺序
3、方法不调用不会执行,因为main方法才是程序的入口
4、void的防范说明方法内是不能有返回值的,但是可以写一个单独的return;来终止方法
return的作用
1、用来终止该方法
2、用来将接收到的值返回个调用者
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只支持单继承,不支持多继承,但允许多层继承
继承的好处和弊端:
好处:提高了代码的复用性,维护性,继承是多态的前提
弊端:提高了代码的耦合性(耦合就是联系的意思)
开发中的原则:高内聚,低耦合
合成复用原则
class Computer {
public void playGame(){
System.out.println("电脑运行游戏");
}
}
class Student {
public Computer dianNao = new Computer();
}
class Demo {
public static void main(String[] args){
Student stu = new Student();
stu.dianNao.playGame();
}
}
继承的特点:
1、在Java中只支持单继承,不支持多继承
2、Java中类支持多层继承
3、父类不能调用子类
继承中的成员特点:
成员变量:在子类方法中访问一个变量
首先在子类局部范围找
在子类成员范围找
最后在父类成员范围找
this和super
this代表本类对象的引用
super代表父类对象引用
注:如果子父类中出现了重名的成员变量,通过就近原则,优先使用子类的,如果使用父类的可以通过supper进行区分
成员方法:
子类成员范围找
案例1:
public class Person {
public void show(){
System.out.println("helloworld");
}
}
public class Student extends Person {
}
public class Demo {
public static void main(String[] args){
Student stu = new Student();
stu.show();
}
}
父类成员范围找
案例2:
public class Person {
public void show(){
System.out.println("fu");
}
}
public class Student extends Person {
public void show(){
System.out.println("zi");
}
//super.show(); // 编译报错 所有的代码(除了定义成员变量的语句)全部都要写在方法里面 因为你不写在方法里面 根本就没法执行。
public void method(){
super.show();
}
}
public class Demo {
public static void main(String[] args){
Student stu = new Student();
stu.show(); //zi
//super.show();
/*
错的原因
1: super 放在哪个类中,就代表的是哪个类的父类。
2: main是static的 静态的里面 不能有this 更何谈super呢
*/
stu.method(); // fu
Person p = new Person();
p.show(); // fu
}
}
重写
注:在继承体系中,子类出现和父类一摸一样的方法声明,这就是方法重写。
注意事项:
私有的(子类看不到的)方法不能重写
//A包
public class Fu {
void show(){
System.out.println("fu");
}
}
//B包
import A包.Fu;
public class Zi extends Fu {
public void show(){ // 正确
System.out.println("zi");
}
}
//B包
import A包.Fu;
public class Zz extends Fu {
@Override // 编译报错 这个@Override的意思是, 这个@Override下面的那个方法 是重写的父类的方法。 然而私有的不能被重写 所以 报错。
public void show(){ //
System.out.println("zz");
}
}
static的方法不能重写。
当子类出现了和父类相同的方法声明的静态方法,可以理解为子类的静态方法只是覆盖了父类的静态方法。
class Fu {
public static void show(){
System.out.println("fu");
}
}
class Zi extends Fu {
public static void show(){ //正确
System.out.println("zi");
}
}
class Zz extends Fu {
@Override //编译报错 因为你加了之后, 这个@Override的意思是, 这个@Override下面的那个方法 是重写的父类的方法。 然而私有的不能被重写 所以 报错。
public static void show(){
System.out.println("zi");
}
}
子类重写父类的方法 权限必须大于等于父类的权限
class Fu {
public void show(){
System.out.println("aaa");
}
}
//同一个包下
class Zi extends Fu {
void show(){ // 编译报错 因为我看的到 父类的show方法,子类写show 就是自动在重写, 重写权限修饰符却小于父类 所以报错
System.out.println("aaaaa");
}
}
子类在创建对象的时候,必须去调用父类的构造方法(语法规定),为什么语法这个规定呢?
是因为:子类创建对象要使用的时候,保不准就会用到父类的内容, 所以 一定是先去把父类在内存中初始化,那么如何把父类在内存中初始化么,
就是访问父类的构造方法,这就是在内存中初始化。 父类初始化完了之后,子类再继续初始化子类的。
因此构造方法的访问特点如下:
1、子类的所有构造方法的第一行 都有一个默认的super() 用于访问父类的无参构造
2、当子类自己写出 super() 、super(参数) 、this() 等 系统就不在会默认提供super()
3、访问构造方法的语句 必须放在 构造方法的第一行
4、当父类没有无参构造方法的时候 子类初始化之前 先初始化父类。
5、子类初始化之前,必须先初始化父类 初始化一次(有且只有一次) 而this和super都放在构造方法的第一行,所以他俩不能共存
注:子类初始化之前必须先初始化父类,而且必须是初始化一次。
多态
多态的前提
1、必须要有 继承或实现关系
2、必须要有 父类的变量(引用) 指向子类对象
class Animal {
}
class Cat extends Animal{
}
Cat c = new Cat(); //猫是一只猫
Animal a = new Cat(); // 猫是一只动物 // 多态
interface Jumping{
}
class Dog implements Jumping{
}
Jumping j = new Dog(); // 多态
3、有方法的重写(非必须)
之所以很多的教程中说必须有方法的重写,是因为 我们去应用多态的时候99%以上的情况 全部都是用方法的重写, 如果你没有方法的重写,那么你使用多态就没有任何意义,所以 很多的教程中 必须有方法的重写。
多态中成员访问特点:
成员变量:
class Person {
int age = 80;
int a = 20;
}
class Student extends Person {
int age = 25;
int b = 30;
}
class Demo {
public static void main(String[] args){
Person p = new Student();
System.out.println(p.age); // 80
System.out.println(p.a); // 20
//System.out.println(p.b); // 编译报错
}
}
结论:多态中调用成员变量 全部都是调用父类的 父类没有就编译报错。
成员方法:
class Person {
public void show(){
System.out.println("Person");
}
public void method(){
System.out.println("method");
}
}
class Student extends Person {
public void show(){
System.out.println("Student");
}
public void function(){
System.out.println("function");
}
}
class Demo {
public static void main(String[] args){
Person p = new Student();
p.show(); // Student
p.method(); // method
//p.function(); //编译报错, 因为父类没有
}
}
结论: 多态中调用成员方法 除了重写的方法 是调用子类的 其他的方法都是调用父类的 父类没有就报错。
总体结论: 多态中除了重写的方法是调用的子类的 其他所有所有的东西都是调用父类的 如果父类没有就报错。
多态的好处:提高了程序的扩展性
设计程序原则:高内聚、低耦合
开发写代码的原则:尽量提高代码的复用性和扩展性
模拟一个场景: 饲养员 饲养 动物园里面的动物。
abstract class Animal{
public abstract void eat();
}
class Cat extends Animal{
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗吃骨头");
}
}
class Tiger extends Animal{
public void eat() {
System.out.println("老虎吃羊肉");
}
}
class Feeder{
public void feed(Animal a){ // Animal a = new Tiger();
a.eat();
}
/*
public void feed(Cat a) {
a.eat();
}
public void feed(Dog a) {
a.eat();
}
public void feed(Tiger a) {
a.eat();
}
*/
}
public class Demo {
public static void main(String[] args) {
Feeder f = new Feeder();
Cat c = new Cat();
f.feed(c);
Dog d = new Dog();
f.feed(d);
Tiger t = new Tiger();
f.feed(t);
}
}
多态的弊端:不能调用子类特有的内容
举例
class Fu {
}
class Zi extends Fu {
public void method(){
}
}
class Demo {
public static void main(String[] args){
Fu f = new Zi();
//f.method(); // 弊端: 不能调用子类特有的方法
Zi z = (Zi)f;
z.method(); //正确的 可以调用。
}
}
解决:类型转换
基本类型
自动类型转换(隐式转换)
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 如果不是 那么这个表达式就是false
boolean b = a1 instanceof Cat;
if (b){
Cat c1 = (Cat)a1;
}
接口的作用
接口的两个作用:规范性、扩展性
规范性: 代码中怎么体现规范性呢
public interface Spile {
public abstract void show(); // 接口的规范性 就体现在 抽象方法中, 因为子类 实现接口 必须要全部重写里面的抽象方法 而且方法声明一摸一样,你不一样 就报错, 所以这不就是强制性嘛。
}
class Plug implements Spile {
public void show(){
}
}
拓展性: 你只要按照接口的规则 去创建的, 就能和接口对接上,所有按照接口创建的 都可以对接上,所以这就是拓展性。
代码中怎么插进去呢??
这个必须要学习多态后可以能知道。
接口的真实作用:解耦合
接口开发中的应用举例:
接口被设计为支持运行时动态方法解析。通常情况下,为了能够从一个类中调用另外一个类的方法,
在编译时这两个类都需要存在,进而使Java编译器能够进行检查以确保方法签名是兼容的《Java8编程参考官方教程》
// 模拟生活中的一个场景
// 电脑可以 通过 usb接口 使用 鼠标设备。
/*
class Computer {
public void open(){
System.out.println("电脑开机");
}
public void close(){
System.out.println("电脑关机");
}
public void useDevice( Mouse m){
m.run();
}
}*/
interface USB {
void run();
}
class Computer {
public void open(){
System.out.println("电脑开机");
}
public void close(){
System.out.println("电脑关机");
}
public void useDevice( USB u){
// USB u = new Mouse();
u.run();
}
}
class Mouse implements USB {
@Override
public void run() {
System.out.println("鼠标有左键 右键 运行起来啦");
}
}
public class ComputerTest {
public static void main(String[] args) {
Computer c = new Computer();
Mouse m = new Mouse();
c.useDevice(m);
}
}
美团外卖 --- 高德地图
高德地图 设计一套接口。 把这套接口 (class文件) 发给 美团。
美团,把这套 接口(第三方类库) 导入到自己项目中,
美团接下来开发的时候, 他需要调用高德地图的功能的时候, 调用接口就行了。(所以 美团开发项目的时候 不需要 高德地图的人员过来。)
多态优化学生管理系统
传统不分包
class Demo {
//private static Student[] stus = new Student[5];
private static ArrayList<Student> al = new ArrayList<>();
public static void main(String[] args){
// 判断数组是否满了
// 判断是否重复
// 不满 不重复 才添加
//.....
}
}
分包优化
专门和用户交互的 Controller
class StudentController {
private StudentService ss = new StudentService();
public void 界面(){
// 让用户录入
// 当用户录入 1 则表明添加学生
// 此时调用 本类的 addStudent()方法
}
public void addStudent(){
// 录入学生
boolean b = ss.addStudent(stu);
if (b){
System.out.println("添加成功");
}
}
}
专门操作业务的 Service
class StudentService {
//private StudentDao sd = new StudentDao();
//private OtherStudentDao sd = new OtherStudentDao();
private BaseStudentDao sd = DaoFactory.getDao();
public boolean addStudent(Student stu){
return sd.addStudent(stu);
}
}
加一层 -----这一层的作用 用来获取 Dao的。
class DaoFactory {
public static BaseStudentDao getDao(){
return new OtherStudentDao();
}
} // 工厂设计模式。
专门操作数据的 Dao
class BaseStudentDao {
public boolean addStudent(Student stu){
return true;
}
}
class StudentDao extends BaseStudentDao{
private static Student[] stus = new Student[5];
public boolean addStudent(Student stu){
// 添加到数组
return true;
}
}
class OtherStudentDao extends BaseStudentDao {
private static ArrayList<Student> al = new ArrayList<>();
public boolean addStudent(Student stu){
// 添加到集合
return true;
}
}
历史遗留问题:方法的重写
1、私有的不算重写?
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
//f.show(); // 报错 说明 不是重写
//show 是私有的 只能在 class fu 里面去用的。
f.method();
}
}
class Fu{
private void show(){
System.out.println("fu");
}
public void method(){
}
}
class Zi extends Fu{
public void show(){
System.out.println("zi");
}
}
2、静态的不算重写?
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
f.show(); //fu
}
}
class Fu{
public static void show(){
System.out.println("fu");
}
}
class Zi extends Fu{
public static void show(){
System.out.println("zi");
}
}
3、方法的声明一模一样?返回值可以不一样(子类返回值必须是父类返回值的子类)
之前我们总说 重写父类的方法 方法声明必须一模一样。 其实返回值可以不一样。
这个不一样,也是有关系的
子类方法的返回值类型 必须 是 父类方法返回值类型的子类。
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
Person show = f.show();
System.out.println(show); //test.day04.demo1.Student@f5f2bb7
}
}
class Fu{
public Person show(){
//System.out.println("fu");
return new Person();
}
}
class Zi extends Fu{
public Student show(){
//System.out.println("zi");
return new Student();
}
}
class Person{}
class Student extends Person{}
关键字
private
private只能修饰成员,不能修饰局部,被private修饰的成员不能被外部访问
被private修饰的成员变量可以通过set/get方法进行赋值/访问
this
this代表的是对象,指的是一个“动态”对象。谁调用包含this的这个方法,谁就是this
this打破就近原则:局部变量是无法通过对象直接进行调用的
super
static
static关键字注意事项:
1、静态的东西 可以被该类所有的对象所共享 因为静态的东西是凡在方法区的静态区中
2、静态的东西 可以用对象名进行调用,也可以用类名进行调用,但是推荐用类名进行调用
3、静态的随着类的加载而加载进来的 。比创建对象要加载的早。非静态的随着对象的创建而存在,比静态加载的晚 因此,静态的不能直接访问非静态的。非静态的可以直接访问静态的。
4、静态的方法中不能有this——this指的是谁调用包含this的这个方法,随就是this,但是静态加载的比对象早,是随着类加载而加载的并没有上一级
5、静态代码块
局部代码块
目的是让你尽早的吧变量从内存中清理出去
构造代码块
每次调用构造方法,都会执行构造代码块,并且都是在构造方法之前执行
作用:可以把构造方法中反复的代码提取放到构造代码块中
静态代码块
随着类的加载而执行,而且仅执行唯一一次
final
能够修饰类,修饰的类不能被继承
能够修饰方法,被修饰的方法不能被重写
能够修饰变量,修饰的变量变成了常量(只能赋值一次)
字面值常量:能够直接写出来的值
自定义常量:被final修饰的变量,就变成了常量,只能赋值一次
常量的起名规则(回顾标识符)
硬性要求:以数字、大小写字母、_、$组成
软性要求:
1、不能以数字开头
2、不能是关键字
自定义常量的形成
基本类型
class Demo {
public static void main(String[] args){
final int A = 10;
//A = 20; //编译报错
}
}
引用类型
class Demo {
public static void main(String[] args){
final int[] ARR = new int[]{3,4,5}; // ARR 记录的是 地址 比如0xab4561
//ARR = new int[3]; // 编译报错。
ARR[0] = 30; //正确的 引用类型的常量 记录的地址是绝对不能变的,但是地址所指向的空间里面的内容可以变。
}
}
自定义常量的分类
局部常量:赋值之后不能重新赋值
class Demo {
public static void main(String[] args){
final int A; //正确的。 局部常量使用之前必须先赋值。 只不过 赋值之后就不能再次赋值了
/*int A;
A = 20;
System.out.println(A);
A = 30;
System.out.println(A);*/
/*
final int A;
A = 20;
System.out.println(A);
//A = 30; // 编译报错。
*/
}
}
成员常量
案例1:
class Student{
final int A; //A 是成员 有默认值 0 那你为什么给我报错啊?
// 就是因为你有默认值才给你报错
// 常量只能赋值一次, 如果你有默认值, 就相当于你已经让默认值赋值了。 今后就再也不能赋值了,一辈子只能用默认值了
// 但是你要知道 默认值 是用来占位的
// 不是为了让你去用的。 想让你珍惜这个常量的赋值机会。
}
案例2:
class Student{
final int A = 10; //正确了。
}
案例3:
class Teacher {
final int A = 20;
}
class Student{
final int A = 10;
}
class Demo {
public static void main(String[] args){
//System.out.println(A);// 编译报错
//System.out.println(Student.A); // A 被final修饰 并不是被static修饰啊。 所以不能用类名调用。
Student s = new Student();
System.out.println(s.A); //10
// 我在使用这个A之前 A必须不能是默认值。 只要在A存在之前 A是绝对不能是默认值的。
// 也就是说 在使用A之前 而A只能是由对象来去调用,所以 在Student 创建学生对象之前 你能够把A的默认值改掉就行了。
// 不非得 定义的时候 直接写死。 final int A = 10;
// 你想啊 创建对象之前都做了哪些事情啊?
// 静态代码块 构造代码块 构造方法
}
}
案例4:
class Student {
public final int a;
{ //构造代码块
a = 20; //正确
}
}
案例5:
class Student {
public final int a =10;
{
a = 20; //编译报错 final修饰的数据 只能赋值一次。 而你现在赋值两次
}
}
案例6:
class Student {
public final int a ;
static {
a = 20; //肯定编译报错 因为 static 是先人 a是后人 先人不能访问后人的东西
}
}
案例7:
class Student {
public static final int a ;
static {
a = 20; // 正确
}
}
class Demo {
public static void main(String[] args){
System.out.println(Student.a);
}
}
案例8:
class Student {
public static final int a ; //
{
a = 20; // 构造代码块是后人 a是先人 虽然可以访问 但是 a是静态的 可以用类名使用, 因为在类加载的时候去把 默认值取消掉。
//而构造代码块只能在创建对象的时候执行,已经晚了。
}
}
class Demo {
public static void main(String[] args){
System.out.println(Student.a);
}
}
案例9:
class Student {
public final int a ;
public Student(){
a = 10; // 正确
}
}
案例10:
class Student {
public final int a ;
{
a = 20;
}
public Student(){
a = 10; // 编译报错 因为赋值了两次。
}
}
案例11:
class Student {
public final int a=20 ;
public Student(){
a = 10; // 编译报错 因为赋值了两次。
}
}
案例12:
class Student {
public final int a;
public Student(int age){
a = age;
}
public Student(){
a = 10;
}
}
class Demo {
public static void main(String[] args){
Student s = new Student(20);
System.out.println(s.a); //20
//s.a =30; //编译报错
Student s = new Student(30);
System.out.println(s.a); //30
}
}
案例13:
class Student {
public final int a ; //编译报错
public Student(int age){
}
public Student(){
a = 10;
}
}
class Demo {
public static void main(String[] args){
Student s = new Student();
System.out.println(s.a); //10
Student s = new Student(20);
System.out.println(s.a); //只能是默认值 而成员常量 不能是默认值、
}
}
案例14:
class Student {
public final int a ;
{
a=20; //正确
}
public Student(int age){
}
public Student(){
}
}
案例15:
class Student {
public final int a =20; // 正确
public Student(int age){
}
public Student(){
}
}
案例16:
class Student {
public final int a; // 正确
public Student(int age){
a=20;
}
public Student(){
this(10);
}
}
抽象类:
定义:一个类中如果出现类使用abstract关键字修饰的方法,则是抽象方法,那么这个类就必须定义为抽象类。
为什么包含抽象方法的类必须是抽象类?
如果一个普通类中包含一个抽象方法,而普通类是可以创建对象的。 Animal a = new Animal(); a.show(); //show方法是抽象的,没有方法体的,执行是没有任何的意义,因此是矛盾的 所以java的设计者 针对这个矛盾 就提出了语法 如果一个类中包含抽象方法,这个类就必须定义为抽象类。 因为抽象类是没有办法创建对象的。
特点:
1、抽象类不能创建对象
(1)、为什么抽象类不能创建对象? 因为抽象类中可能存在抽象方法,如果允许抽象类创建对象,那么当对象去调用重选ing方法时,却没有任何的意义,因此互相矛盾
(2)、抽象不能创建对象是不是因为它没有构造方法?
抽象类是有构造方法的, 他的构造方法 并不是为了让他创建对象用的, 而是用来让子类继承他的时候 初始化他的。
那么究竟是谁起到了 抽象不能创建对象的作用呢 abstract 这个关键字
2、抽象类里面 可以没有抽象方法,但是有抽象方法的类 必须是抽象类
3、普通类继承抽象类,则必须重写里面的全部抽象的方法,不重写就报错。
注:抽象类继承抽象类 就不是必须要重写了
接口
数据类型
基本类型
引用类型
数组 [ ]
类
接口
定义
public interface Spile {
}
接口特点
1、接口不能创建对象
抽象类不能创建对象
abstract class Person {
public abstract void show();
}
// Person p = new Person(); p.show(); //无内容 怎么调用 矛盾了。
// abstract 修饰的类 就是不能创建对象。
//抽象类创建不了对象 是不是因为他没有构造方法啊。
class Student extends Person {
public Student(){
super(); // Person的无参构造 说明 抽象类是有构造方法的。
}
}
接口为什么不能创建对象?
interface Spile {
public abstract void show();
}
//Spile s = new Spile(); s.show(); //无内容 怎么调用 矛盾了。
// interface 接口不能创建对象。
//接口不能创建对象 会不会是因为他没有构造方法呢?
class Student /*extends Object*/implements Spile {
public Student(){
super(); //访问的Object的无参构造 证明了接口没有构造方法。
}
}
2、子类和接口的关系是实现关系,而且可以多实现
回顾单继承
class Fu1 {
public void show(){
System.out.println("fu1");
}
}
class Fu2 {
public void show(){
System.out.println("fu2");
}
}
class Zi extends Fu1 ,Fu2 {
}
Zi z = new Zi();
z.show(); // 子类不知道调用的哪个父类的 所以矛盾了 所以 java规定不能多继承。
接口为什么能多实现?
interface Fu1 {
public void show(){ //编译报错 接口里面 就不允许存在 普通的有方法体的方法。
System.out.println("fu1");
}
}
interface Fu2 {
public void show(){ //编译报错
System.out.println("fu2");
}
}
class Zi implements Fu1 ,Fu2 {
}
Zi z = new Zi();
z.show();
既然上面的矛盾 那下面这个例子呢?
interface Fu1 {
public abstract void show();
}
interface Fu2 {
public abstract void show();
}
class Zi implements Fu1 ,Fu2 {
public void show(){ //你只要重写了一个 两个接口里面的show 就都重写了。
System.out.println("show1");
}
/*
public void show(){
System.out.println("show1");
}
*/
}
3:子类实现接口 必须重写里面的所有抽象方法。 而这也正是接口具有规范性的体现。
为什么子类要全部重写接口中的全部抽象方法?
interface Spile {
public abstract void show();
}
class Student implements Spile {
}
Student s = new Student();
s.show(); // 内容呢?? 矛盾了。
根据以上 ,推导以下案例:
interface Spile {
public abstract void show();
}
abstract class Student implements Spile { //正确的
}
//Student s = new Student(); //不能创建对象
4:接口的作用 有两个 规范性和扩展性
规范性:
public interface Spile {
public abstract void show(); // 接口的规范性 就体现在 抽象方法中, 因为子类 实现接口 必须要全部重写里面的抽象方法 而且方法声明一摸一样,你不一样 就报错, 所以这不就是强制性嘛。
}
class Plug implements Spile {
public void show(){
}
}
扩展性:
你只要按照接口的规则 去创建的, 就能和接口对接上,所有按照接口创建的 都可以对接上,所以这就是拓展性。
代码中怎么插进去呢??
这个必须要学习多态后可以能知道。
注: 接口的真实作用其实是解耦!
接口的成员
接口没有构造方法!
成员变量
接口中的成员变量 全部都是 常量 前面都有默认修饰符 public static final
案例1:
interface Law {
int a= 10; //正确 即使你什么修饰符都不写 那么这个变量前面也会有默认的修饰符 public static final
}
案例2:
interface Law {
int a; // 编译报错 常量 不能是用默认值
}
interface Law {
int a;
public Law(){ // 编译报错 接口没有构造方法
a = 20;
}
}
interface Law {
int a;
{ //为什么叫做构造代码块 因为他是为构造方法而服务的, 接口中没有构造方法。 所以 java规定 接口中不能存在构造代码块的
a = 20;
}
}
interface Law {
int a;
static { //编译报错 静态代码块 不允许存在于接口中, 是因为 早期jdk1.7之前 接口就是作为一种规范 不想让他具备有逻辑的语句。
a = 20;
}
}
interface Law {
int a =10;
}
成员方法
jdk1.8之前:接口中的方法 只能有抽象方法 即使你什么修饰符都不写 也有默认的 public abstract
jdk1.8之后
出现默认方法
案例1:
interface Dao {
/*
void show(){ //如果你要 直接什么修饰符都不加 系统默认给你加 public abstract
//编译报错
}
*/
default void show(){
System.out.println("接口中就具备了 具有功能的方法");
}
}
案例2:
interface Dao {
default void show(){
System.out.println("接口中就具备了 具有功能的方法");
}
}
class StudentDao implements Dao {
}
class Demo {
public static void main(String[] args){
//Dao.show(); // 编译报错 show是静态的吗 不是 就不能用类名去调用。
//Dao d = new Dao();
//d.show(); // 编译报错 接口不能创建对象
//StudentDao.show(); // 编译报错。
StudentDao sd = new StudentDao();
sd.show(); //正确 对
}
}
案例3:
interface Dao {
/*public */default void show(){
System.out.println("接口中就具备了 具有功能的方法");
}
}
class StudentDao implements Dao {
void show(){ //编译报错 子类重写父类的方法 权限修饰符必须大于等于父类权限修饰符。
System.out.println("重写show方法");
}
}
案例4:
interface Dao {
default void show(){
System.out.println("Dao");
}
}
interface Service {
default void show(){
System.out.println("Service");
}
}
class Student implements Dao, Service{
/*
void show(){
System.out.println("子类必须重写 补重写就报错。");
}
*/
}
Student s = new Student();
s.show(); //???矛盾 java规定
出现静态方法
类中的静态方法
class Person {
public static void show(){
System.out.println("person");
}
}
class Student extends Person {
}
class Demo {
public static void main(String[] args){
Student s = new Student();
s.show(); // 可以调用
Student.show(); // 可以调用
Person p = new Person();
p.show(); //可以调用
Person.show(); //可以调用 (推荐)
}
}
接口中的静态方法
interface Dao {
static void show(){
System.out.println("接口static方法");
}
}
class StudentDao implements Dao {
}
class Demo {
public static void main(String[] args){
StudentDao sd = new StudentDao();
//sd.show(); // 编译报错。
StudentDao.show(); // 编译报错
Dao d = new Dao(); //编译报错
d.show();
Dao.show(); // 正确
// 接口中的 static方法 是给自己准备的 子类无论如何调用不了。
}
}
jdk1.9之后 :出现私有方法
public interface Dao {
default void show(){
System.out.println("haha");
chongfu();
}
private void chongfu(){
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
}
default void method(){
System.out.println("xixi");
chongfu();
}
static void show1(){
System.out.println("haha");
chongfu1();
//chongfu();
}
private static void chongfu1(){
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
}
static void method1(){
System.out.println("xixi");
chongfu1();
}
}
类和接口的关系
类和类:继承关系,可以单继承,不能多继承,但可以多层继承
class Fu1 {
public void show(){
System.out.println("fu1");
}
}
class Fu2 {
public void show(){
System.out.println("fu2");
}
}
class Zi extends Fu1 ,Fu2 {
}
Zi z = new Zi();
z.show(); // 子类不知道调用的哪个父类的 所以矛盾了 所以 java规定不能多继承。
class YeYe {
public void show(){
System.out.println("YeYe");
}
}
class Fu extends YeYe{
public void show(){
System.out.println("YeYe");
}
}
class Zi extends Fu {
}
Zi z = new Zi();
z.show(); // 调用的父类的
类和接口:实现关系,可以单实现,也可以多实现
案例1:
interface Fu1 {
public abstract void show();
}
interface Fu2 {
public abstract void show();
}
class Zi implements Fu1 ,Fu2 {
public void show(){ //你只要重写了一个 两个接口里面的show 就都重写了。
System.out.println("show1");
}
/*
public void show(){
System.out.println("show1");
}
*/
}
案例2:
interface Fu1 {
public abstract String show();
}
interface Fu2 {
public abstract void show();
}
class Zi implements Fu1 ,Fu2 {
public void show(){
System.out.println("show1");
}
public String show(){ // 方法重复了。
return "abc" ;
}
}
/*
java规定不允许出现这种情况。
*/
案例3:
interface Dao {
default void show(){
System.out.println("abc");
}
}
class Person {
public void show(){
System.out.println("bcd");
}
}
/*
class Student implements Dao extends Person { //编译报错
}
*/
class Student extends Person implements Dao {
}
Student s = new Student();
s.show(); //bcd
// java规定 当一个类中的方法 和 接口中的 默认方法方法声明一样的时候, 子类优先使用父类的。
案例4:
interface Dao {
public abstract void show();
}
class Person {
public void show(){
System.out.println("bcd");
}
}
class Student extends Person implements Dao {
// 正确
// Student 没有重写 Dao的 show方法也不报错。
// 因为你继承的Person里面有一个show
// 你继承了Person 不就相当于你自己具备了一个show嘛 也就算重写了
}
接口和接口:继承关系,可以单继承,也可以多继承
案例:
interface Fu1 {
default void show(){
System.out.println("abc");
}
}
interface Fu2 {
default void show(){
System.out.println("bcd");
}
}
interface Zi extends Fu1,Fu2 {
// java规定 如果一个接口 继承的多个接口里面 有重复的 默认方法, 那么 子接口 必须重写 这个方法。
default void show(){
System.out.println("ziji de ");
}
}
class ZiZi implements Zi {
}
ZiZi z = new ZiZi();
z.show();
抽象类和接口的区别
设计理念不同!
内部类:
访问特点
内部类访问外部类的内容 直接可以访问的,包括私有的。
外部类要想访问内部类的内容, 必须创建内部类的对象 对象调用。
public class Outer {
private int age = 10;
public class Inner {
public int leg = 5;
private int hehe = 20;
//System.out.println(age); // 编译报错 除了定义变量类的语句 可以直接写在成员位置(成员变量) 其他所有的语句 比如 打印语句 if语句 for语句都要写在方法里面
public void show(){
System.out.println(age); // 正确 为什么可以访问 age 的private的作用范围 是 Outer内, show就是在outer内的。
System.out.println(leg); // 正确 就近原则。
}
}
public void method(){
System.out.println(age); //正确
// System.out.println(age); // 其实这句话底层是这么写的 System.out.println(this.age);
System.out.println(leg); // 编译报错
// this.leg Outer的成员位置 是没有leg的。
// leg 是非静态的成员变量 需要用对象来调用的。 leg是 Inner类的成员变量 那么就需要用Inner的对象来调用
Inner in = new Inner();
System.out.println(in.leg); //正确
System.out.println(in.hehe); // 正确 private修饰的hehe 不仅能在Inner 里面去访问的,也可以在外部类的内部访问, 因为private的权限可以扩大到最外层的那个类的内部。
System.out.println(xixi); //编译报错 xixi在function的方法里面 他是局部变量 只在局部有效。 是没有办法 从一个局部 直接调用另一个局部的东西
//function f = new function();
//System.out.println(f.xixi);
int a = getX();
System.out.println(a); // 正确。
}
public void function(){
int xixi = 30;
}
public int getX(){
int x = 10;
return x;
}
}
内部类分类
public class Outer {
// 成员位置 去定义类 : 成员内部类
public void show(){
// 局部位置 去定义类 : 局部内部类。
}
}
成员内部类
1、内部类创建对象的格式理解
public class Outer {
int a = 10;
public class Inner{
public void method(){
System.out.println("内部类的method方法");
}
}
}
public class Test {
public static void main(String[] args) {
//创建内部类对象。
/* Outer.Inner oi = new Outer().new Inner();
oi.method();*/
//Inner i = new Outer().new Inner();
/*
Outer.Inner 这么写是因为你直接写 Inner 找不到 Inner在哪
你能找到的是Outer类。 所以用Outer 限定一下, 告诉jvm Inner是在Outer里面
oi 是变量名 随便起名。
new Outer().new Inner(); :这儿为什么是这样写的呢?
我想请问你一下。Inner是属于Outer的成员。 非静态的成员,随着对象的存在而存在的。
如果没有Outer对象,请问 Outer对象的里面的成员 能不能用》??? 不能使用。
Inner 是Outer的成员。
所以 要想有Inner的对象, 请必须先给我Outer的对象。
所以写法 new Outer().new Inner();
所以现在把这个写法修改一下 你也就理解了。
*/
Outer o = new Outer();
Outer.Inner oi = o.new Inner();
}
}
2、静态的成员内部类
public class Outer {
int a = 10;
public static class Inner{
public void method(){
System.out.println("内部类的method方法");
}
}
}
public class Test {
public static void main(String[] args) {
Outer.Inner oi = new Outer.Inner();
/*
Inner 是属于 Outer的静态内容。
静态的随着类的存在而存在的, 所以可以用类名调用。
*/
}
}
3、成员内部类访问当前外部类对象的内容
public class Outer {
int a = 10;
public class Inner{
int a = 20;
public void method(){
int a = 30;
System.out.println(a); // 30
System.out.println(this.a); // 20
//System.out.println(super.a); //编译报错
//System.out.println(Outer.a); //编译报错
System.out.println(Outer.this.a); // 5
System.out.println(new Outer().a); //10
}
}
int b = 40;
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.a = 5;
Outer.Inner oi = o.new Inner();
oi.method();
/*
Inner 他是不是属于 Outer的成员。
成员随着 对象存在的。
所以 Inner对象的存在 必须 随着 Outer对象的存在而存在。
开发中 单纯的Outer对象 是不是可以有很多个啊?
我只想找到 Inner对象所在的 Outer对象。
*/
}
}
4、私有成员内部类
public interface Breast {
void beat();
}
public class Person {
private class Heart implements Breast {
@Override
public void beat() {
System.out.println("心脏在跳动");
}
}
public Heart showBreast(){
Heart h = new Heart();
return h;
}
}
public class Test {
public static void main(String[] args) {
//Person.Heart; 编译报错,原因是因为访问不了
Person per = new Person();
Breast breast = per.showBreast(); // new Heart();
breast.beat();
}
}
局部内部类
局部内部类将对象丢到外界使用:
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();
}
}
面试题(☆☆☆☆):
局部内部类访问局部变量,局部变量前面必须有final进行修饰, 在jdk1.8的时候final可以省略,但并不是真正的省略,而是前面会默认添加final关键字,这样做的是为了延长局部变量的生命周期
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();
}
匿名内部类(☆☆☆☆☆)
匿名内部类的由来
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() { // 建一个内部类,只不过这类 没有名字, 所以叫做 匿名的内部类
public void show() {
System.out.println("你的脸有几分憔悴 你的眼有残留的泪");
}
};
f1.show();
/*
匿名内部类的格式:
new 接口名(){
重写方法;
};
*/
}
}
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");
}
};
}
}
匿名内部类的应用场景
当一个接口,只有一个抽象方法的时候,使用匿名内部类是比较合适的,因为这样,使用匿名内部类是比较方便的,建议少建Java文件、少写class类
interface Inter {
void function();
}
class Demo {
public static void main(String[] args) {
/*
Inter inte = new Inter(){
public void function(){
System.out.println("function");
}
};
show(inte);
*/
show(new Inter() {
public void function() {
System.out.println("function");
}
});
}
public static void show(Inter in) {
in.function();
}
}
Lambda表达式
lambda表达式 的格式说明和前提条件
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);
}
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要求接口中只能有一个抽象方法
lambda不会生成class文件
权限修饰符
权限修饰符
private : 给自己准备的
默认的 :给自己家准备的。
protected : 给自己家准备的, 同时也给你在远方的儿子准备的。
public : 给世界准备的。
异常:
泛型 jdk1.5的新特性:
泛型的概述
泛指的是 泛泛的 类型 泛指某一种类型(必须是引用类型)
泛型的创建时机:
1、 创建一个对象的时候明确泛型
2、调用一个方法 的时候明确泛型
3、创建一个类 然后类实现一个接口的时候 明确泛型
泛型的好处
1、能让程序更加的精确 、准确 把一些运行时的错误在编写代码的时候 去避免掉
2、提高程序的 可扩展性 兼容性
泛型的分类
泛型类:
//泛型定义在类上的 叫做泛型类 泛型在类中都可以使用
public class Display1<T> {
public void show(T t){
System.out.println(t);
}
}
public class Display1<T> {
public void show(T t){
System.out.println(t);
}
}
泛型方法:
public class Display2<Q> {
//泛型定义在方法上的叫做泛型方法 泛型只能在当前方法上使用
public <T> void show(T t){
System.out.println(t);
}
public <T> void show(T t,Q q){
System.out.println(t);
System.out.println(q);
}
}
//泛型定义在方法上的叫做泛型方法 泛型只能在当前方法上使用
public <T> void show(T t){
System.out.println(t);
}
public <T> void show(T t,Q q){
System.out.println(t);
System.out.println(q);
}
}
泛型接口:
//泛型接口 可以在实现类中明确泛型的类型
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 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 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);
}
}
}
<? extends >
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);
}
}
}
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);
}
}
}
<? super >
API(核心类库)
Scanner
next()
nextLine()
bug:先nextInt 后nextLine的时候 就会出现nextLine不能录入的情况
解决方法1:
public class Demo1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int anInt = sc.nextInt();
String next = sc.next(); //第一种解决方案: 用next来代替nextLine就可以了。
//但是next是以 "空格类字符"为结束标记的。 当你录入abc bcde 的时候,那么 直接接收 abc
}
}
解决方案2:
public class Demo1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int anInt = sc.nextInt();
sc = new Scanner(System.in);
String next = sc.nextLine(); //第二种解决方案: 使用完 nextInt之后, 新创建一个新的Scanner对象, 用新的Scanner 对象去调用nextLine就可以了。
// 但是 这种 的缺点就是 浪费空间。
}
}
nextInt()
......
Random
String
String的特点:
字符串是常量:在基本数据(常量)中,只有String是引用类型。几乎所有的引用类型中都是综合数据,只有String表示的基本数据。其他的引用类型 必须要new才是一个对象。 但是String直接写一个 基本数据"abc"字符串常量,它就是一个String的对象。
字面值常量:运行过程中,数据不可改变
System.out.println(10);
System.out.println(20);
int a = 10;
a = 20;
System.out.println("abc");
System.out.println("bcd");
String s = "abc";
s = "bcd";
字符串的常见面试题
String s = "abc";
String s1 = "abc";
System.out.println(s.equals(s1)); // true equals比较的是内容 和地址无关。
System.out.println(s == s1); // true 地址值一样
String s = "abc";
String s1 = new String("abc");
System.out.println(s.equals(s1)); //true 内容
System.out.println(s == s1); //false 地址
String s = "abc";
String s1 = "ab"+"c"; // 常量优化
System.out.println(s.equals(s1)); //true 内容
System.out.println(s == s1); //true
String s = "abc";
String s2 = "ab";
String s1 = s2 +"c"; // 变量s2 和常量"c" 没有常量优化机制了。
System.out.println(s.equals(s1)); //true 内容
System.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";
char[] chs = {'a','b','c'};
String s2 = new String(chs); // "abc"
byte[] bys = {97,98,99};
String s3 = new String(bys);
System.out.println(s3); //"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这个索引开始 截取到最后 返回成新的字符串。
public String substring(int start,int end); // 从字符串的start这个索引开始 截取到end的索引 返回成新的字符串。
public String replaceAll(String old , String newStr); 字符串中 用 newStr 这个串 去替换 字符串中 所有old的小串。
public String replace(CharSequence old , CharSequence newStr);
// 字符串中 用 newStr 这个串 去替换 字符串中 所有old的小串。
和replaceAll的区别在于, replaceAll 只能传字符串, 而这个方法可以传字符串也可以传字符。
String s6 = s5.replaceAll("bc","$$");
String s7 = s5.replace('b','@');
public String replaceFirst(String old , String newStr); 字符串中用newStr 这个串 去替换字符串中第一个old的小串。
public String[] split(String regex); // 根据regex 这个小串 把大串切割出来。
boolean contains(CharSequence s) 判断当前字符串是否包含指定的字符串 包含返回为true
StringBuilder
构造方法
StringBuilder sb = new StringBuilder(); //无参
StringBuilder sb = new StringBuilder("abc");
成员方法
public StringBuilder append(任意类型);//拼接字符串
public StringBuilder reverse();//反转字符串
public int length(); 获取StringBuilder里面字符的个数
public String toString();//转换为String类型
String和StringBuilder互相转换
String --> StringBuilder
1: String s = "abc"; StringBuilder sb = new StringBuilder(s);
2:
String s = "abc";
StringBuilder sb = new StringBuilder();
sb.append(s);
StringBuilder --> String
StringBuilder sb = new StringBuilder();
sb.append(true);
String s = sb.toString(); //转换
System.out.println(s); //true
提高拼接效率的原理
String拼接字符串原理
StringBuilder拼接字符串原理
注:打印 char[] String StringBuilder ArrayList 这些打印的时候 直接打印内容 而不是地址
ArrayList
Math
注:Math的方法都是静态的,构造方法是私有的。Math不能创建对象
public static int abs(int a) 返回参数的绝对值 absolute 绝对的
public static int abs(int a){ //返回参数的绝对值 absolute 绝对的
return (a>0)? a : (a<0?-a:a);
}
public static double ceil(double a) 向上取整
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 floor(double a) 向下取整
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 int round(float a) 四舍五入
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 max(int a,int b) 返回两个int值中的较大值
public static int max(int a,int b){ //返回两个int值中的较大值
return (a>b)?a:b;
}
public static int min(int a,int b) 返回两个int值中的较小值
public static int min(int a,int b){//返回两个int值中的较小值
return (a<b)?a:b;
}
public static double pow(double a,double b)返回a的b次幂的值
public static double pow(int a,int b){ //返回a的b次幂的值
double num = 1;
double result = 0;
if (b>=0){
for (int i = 0; i < b; i++) {
num *= a;
}
} else{
b = -b;
for (int i = 0; i < b; i++) {
result = num *= a;
}
num = 1/result;
}
return num;
}
public static double random() 返回值为double的正值,[0.0,1.0)
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;
}
System
注:System的方法都是静态的 System的构造方法是私有的。
成员方法:
public static void exit(int status) 终止当前运行的Java虚拟机
0表示的正常退出虚拟机 非0的表示的异常退出。
public Static long currenTimeMillis() 返回的是一个long类型的计算机时间原点 到当前时间 的毫秒值
arraycopy(Object src , int formIndex , Object dest , int formIndex1 , int len)
arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数)
我要把arr1最后两个元素,拷贝到arr2的最后两个索引上
int [] arr1 = {1,2,3,4,5};
int [] arr2 = new int[10];
System.arraycopy(arr1,3,arr2,8,2);
Object
toString: 类从写了同String ,打印语句就打印toString的内容
equals(): Object 的equals方法是比较的地址。要想让自定义的类的对象比较内容,那么就必须重写equals 方法 来比较内容
public class Student {
private int age;
private int score;
public Student(int age, int score) {
this.age = age;
this.score = score;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return score == student.score;
}
/*@Override
public boolean equals(Object obj) {
// 比较两个对象内容。
// 这里面谁和谁 代表的是比较的两个对象呢?
// this 谁来调用我 我就代表谁。 stu
// obj 代表是 stu1
if (this == obj){
return true;
}
if (obj ==null){
return false;
}
if (!(obj instanceof Student) ){
return false;
}
// obj 虽然是指的stu1
// Object obj = stu1;
// 多态不能使用子类特有的内容。
//boolean b = this.age ==obj.age && this.score == obj.score;
Student stu1 = (Student)obj;
boolean b = this.age ==stu1.age && this.score == stu1.score;
return b;
}*/
}
Objects:是Object类的工具类 里面的方法都是静态方法
public static String toString(对象) 返回参数中对象的字符串表示形式
public static String toStirng(对象,默认字符串) 返回参数中对象的表示形式,如果对象为空,那么就以默认的字符串表示
public static Boolean isNull(对象) 判断对象是否为空
public static Boolean nonNull (对象) 判断对象是都不为空
BigDecimal:精确操控小数的类
构造方法:
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对象 , 精确几位 ,舍入模式) 除法
包装类:
包装类概述:
包装类是将基本类型数据封装成对象 用于基本类型与字符串之间的转换!
包装类分类:
byte --> Byte
short --> Short
int --> Integer
long --> Long
float --> Float
double --> Double
char --> Character
boolean --> Boolean
Integer类构造方法:
子主题 1
子主题 4
子主题 5
Arrays:是Array的工具类
public static String toString(int[] a) 返回指定数组的内容的字符串表示形式
public static void sort(int[] a) 按照数字顺序排列指定的数组
public static int binarySearch(int[] a, int key) 利用二分查找返回指定元素的索引
public static T[] copyOf(T[] original, int newLength) 复制指定的数组,使用零截断或填充(如有必要),以使副本具有指定的长度。
Arrays.asList(arr) 生成的集合 不能增删 只能改查
Date(☆☆☆☆☆)
构造方法
Date date = new Date(); //代表当前时间
Date date = new Date(100L) //代表的是原点时间+传递的毫秒值 所代表的时间
成员方法:让Date 对象 和 基本类型的long的转换
public long getTime() 获取时间对象的毫秒值
public void setTime(long time) 设置时间,传递毫秒值
SimpleDateFormat(☆☆☆☆☆):
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 : 将没有格式 的 时间 转换为一个有时间格式表示时间的字符串
public final String format(Date date) 将日期格式转化为日期/时间字符串
String --> Date : 把有合适的字符串转化为一个计算机可以识别的Date日期对象
public Date parse(Stirng source) 从给定的字符串开始解析文本以生成日期
集合
单列集合:Collection接口
Collection的共性方法:
boolean add(E e) 添加元素
boolean remove(E e) 从集合中移除指定的元素
boolean removeif(Prodicate o) 根据条件进行删除
void clear() 清空集合
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean isEmpty() 判断集合是否为空
int size() 返回集合的长度
Collections:Collection类的工具类
public static<T> void shuffle(List<T> list) 将传入的集合中的元素打乱顺序
List接口
List集合特有的方法:
void add(int index , E e) 在指定索引添加元素
E remove(int index); 删除指定索引的元素,并返回该元素
E set(int index , E e) 修改指定索引的元素 ,并返回被修改的元素
E get(int index ) 返回指定索引上的元素
注:因为List中有特有的get()方法,所以List体系的所有集合,多了一种通过普通for循环的遍历方式
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);
}
public Object[] toArray() 哪个集合调用 就将哪个集合转化为Object类型的数组
public <T> T[] toArray(T[] t); 哪个集合调用 就根据集合的类型转换为集合类型的数组
ArrayList
ArrayList底层源码
ArrayList添加第一个元素执行流程
ArrayList添加第二个元素的执行流程
添加第11个元素的执行流程
注:ArrayList底层数组初始长度是10 每次扩容1.5倍
ArrayList的底层是数组结构
LinkedList
LindedLIst特有方法;
public void addFirst(E e) 在该列表的开头插入指定元素
public void addLast(E e) 将指定的元素追加到该列表的末尾处
public E getFirst() 返回该列表中的第一个元素
public E getLast() 返回该列表的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
底层分析:
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对象中封装了三个数值
上一个元素的地址值 当前值 下一个元素的地址值)
3、LinkedList 只能从头往后 或则从后往前查询 但它却提供了一个get(int index)方法
疑问:LinkedList中并没有索引 但是却有一个get(int index )方法 而它的参数却是需要一个int类型的索引 两者岂不是矛盾?
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方法底层 绝对不是通过索引拿的,
// 而是先去判断 你拿的位置是属于 链表的前半部分 还是后半部分。
// 如果属于前半部分 就会从第一个往后找, 如果属于后半部分 就会从最后一个往前找。
Set接口
Set数组特点: 不能存储重复元素,没有索引 存取无序
TreeSet:去重复、没有索引
TreeSet排序方式
自然排序:
在TreeSet集合中想要存储自定义对象的类型 因为TreeSet集合有按照规则排序的特点 则需要在自定义类中实现 Comparable 接口 并重写抽象方法 compareTo方法 并添加相应的规则
public class TreeSet01 {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
Student stu1 = new Student("张三", 23);
Student stu2 = new Student("李四", 21);
Student stu3 = new Student("王五", 27);
set.add(stu1);
set.add(stu2);
set.add(stu3);
for (Student student : set) {
System.out.println(student);
}
}
}
class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
int result = this.age - o.age;
result = result == 0 ? this.name.compareTo(o.name) : 0;
return result;
}
}
返回值:
0 表示重复,表示当前添加的元素是重复的,不添加。
正数 : 往右添加
负数: 往左添加
比较器排序:
在TreeSet集合使用自定义的对象 如果这个自定义对象不具备(没有实现 Comparable 接口 ) 那么就需要对其提供一个比较规则 叫做比较器 Comparator 里面有一个 compare 方法 方法中添加规则
Set<Student> set1 = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
set.add(new Student("张三", 23));
set.add(new Student("李四", 21));
set.add(new Student("王五", 27));
Set<Student> set = new TreeSet<>((o1,o2)->o1.getAge() - o2.getAge());
set.add(new Student("张三", 23));
set.add(new Student("李四", 21));
set.add(new Student("王五", 27));
for (Student student : set) {
System.out.println(student);
}
第一个参数 是即将添加的元素, 第二个参数 是已经添加过的元素
注:比较器排序优先于自然排序
注: TreeSet底层是红黑树结构
HashSet
哈希值:
1、HashCode()方法 的返回值 就是哈希值
2、HashCode()方法是Object的 所有的对象都可以调用HashCode方法
3、HashCode方法的底层调用的是一个本地函数,通过对象的地址值和算法得到唯一的数字,不同的对象这个 哈希值绝对是不同的
4、类继承了Object 也是可以重写Hash Code方法的 方法重写之后的返回值 就不再代表地址了
什么时候重写hashCode方法和equals方法:
如果判定对象的地址值相同就是一个对象 那么就不能重写equals方法 因为equals方法默认比较的是地址值 底层就是一个 ==
如果判断属性相同的对象是一个元素 那么就需要重写equals方法 和hashCode 方法
HashSet去重原理:
首先 hashSet拿着元素的hashCode的值 和hash表 中所有的元素进行比较 如果不存在 那么就将该元素直接添加到哈希表中 如果存在 那么就然这个元素用equals方法和哈希表中相同的元素进行一一的比较 如果都是false 则添加到集合中
如果有返回为true 则不添加到集合中
LinkedHashSet :
特点:存取有序 、去重 、无索引
底层结构: 底层在使用哈希表的基础上 额外使用了一个链表来存储 是集合存取有序
List接口和Set接口的区别:
List:可以存储重复元素、有索引、有序
Set:存储的元素自动去重、没有索引、无序
双列集合:Map接口
Map方法 :
void put(k key,V value) 添加元素 如果集合中存在相同的键值 那么将替换前一次的value值。
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集合的遍历方式:
1、通过键找值:
Set<String> keySet = map.keySet();
for (String s : keySet) {
Integer value = map.get(s);
System.out.println(s+"->"+value);
}
2、根据键值找键和值:
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
3、forEach:
map.forEach((String key,Integer value)-> System.out.println(key+"--->"+value));
Map的子类:
HashMap
Map 集合: 要求键 不重复的 底层必须要去重
HashMap : 利用的哈希表结构 给键 去重
HashSet 的底层 是使用 HashMap 的键来完成的 值 直接为null
如果我们想要使用HashMap在键位置上 存储自定义类对象
1、如果 我们认为每 new 一个自定义类对象 就是一个不同的对象 就不能去重 不需要重写equals 和 HashCode方法
2、如果我们认为自定义类对象 的属性 如果相同了 就认为 是同一对象 需要去掉重复 就需要重写equals 和 HashCode方法
TreeMap
Map 集合:要求键与值 不重复 底层必须要去重
TreeMap :利用的红黑树结构 给键 去重的
TreeSet 的 底层就是受用的TreeMap的键来完成的 值的位置直接设置为null
如果我们想要使用TreeMap在键的位置存储自定义的类对象
1、如果自定义类什么都不继承 什么都不实现 直接让TreeMap的键位置存储 那么将会运行报错 因为TreeMap 的键 是红黑树结构 是需要排序的 然而自定义类并没有提供排序规则 所以存储添加的时候报错
2、正是因为 TreeMap 的键是红黑树结构的 所以想要使用自定义类必须提供排序规则
1、让自定义类 实现Comparable 接口 重写CompareTo方法 一个参数 this - 参数 :升序
2、创建TreeMap的时候在构造方法的参数上传递一个Comparator 的子类对象Compare 两个参数 o1-o2升序
双列集合的特点: 键 和 值 都是一一对应的
键是不能重复的 值可以重复
键和值的关系 就像是 身份证上的身份证号 和姓名 身份证号 有且只有相对应的人 而姓名却可以有多个人用一个姓名
迭代器与增强for
迭代器:
单例集合通用的遍历方式出现了迭代器进行遍历
/*
集合调用迭代器方法,返回迭代器接口对象
*/
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("a".equals(s)){
it.remove();
}
}
注:并不是只有单例模式可以使用迭代器,只要实现了Iterator接口的子类就可以创建迭代器
增强for:
注:增强for是为了简化迭代器二出现的,底层就是使用的迭代器
格式:
for (元素类型 变量名 : 集合) {
//语句体
}
增强for的目标:什么内容可以使用增强for循环遍历呢?
所有实现了Iterable接口的 类的 对象都可以被增强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的比较:
增强for中是不会出现索引的 因为底层是使用的迭代器,然而 迭代器没有索引
普通for中 是由索引的
注:数组与集合之间的转化:
集合转数组:
Object[] toArray () 以正确的顺序(从第一个到最后一个) 返回到包含所有元素的数组中
<T> T[] toArray(<T> t) 返回一个包含所有元素的数组 该数组的类型是指定数组的运行时类型
数组转集合:
Arrays --> static <T> List<T> asList(T... a) 生成的集合 不能增删 只能改查
List.of(arr) 生成的集合不能增删改 可以查
集合总结:
Stream流:
Stream流介绍: Stream使用来优化操作集合代码的
Stream流的原理 : Stream是基于匿名内部类的延迟执行现象
Stream流的三类方法:
生成流方法:
1、collection体系的集合可以使用默认方法 stream()生成Stream流
default Stream<E> stream()
2、Map体系的集合间接生成流
3、数组可以通过Stream接口的静态方法of(T ... values)生成流
中间操作方法:
常见的创建中间方法
Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
Predicate接口中的方法 boolean test(T t):
对给定的参数进行判断,返回一个布尔值
Stream<T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
tatic <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
中间方法
Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流 去重
Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序
Stream<T> sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
Comparator接口中的方法 int compare(T o1, T o2) 比较器排序
<R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
Function接口中的方法 R apply(T t)
IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
IntStream:表示原始 int 流
ToIntFunction接口中的方法 int applyAsInt(T value)
终结方法:
void forEach(Consumer action):对此流的每个元素执行操作
Consumer接口中的方法 void accept(T t):
对给定的参数执行此操作
public class Demo02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.addAll(List.of("张三峰","张翠山","张无忌","张三","李四","王二麻子"));
//内部类:
method1(list);
//Lambda:
method2(list);
//Lambda 优化:
method3(list);
}
private static void method3(ArrayList<String> list) {
list.stream().filter((s -> s.startsWith("张"))).forEach(s -> System.out.println(s));
}
private static void method2(ArrayList<String> list) {
list.stream().filter((String s) -> {
return s.startsWith("张");
}
).forEach(s -> System.out.println(s));
}
private static void method1(ArrayList<String> list) {
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
}).forEach(s -> System.out.println(s));
}
}
long count():返回此流中的元素数
Stream流收集方法:R collect(Collector collector)
它是通过工具类Collectors提供了具体的收集方式 collect方法只能获取到流中的剩余的每一个数据 在底层并不能创建容器 也不能把数据添加到容器当中
public static <T> Collector toList():把元素收集到List集合中
public static <T> Collector toSet():把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
I/O流:
File:
File的概述:
File代表文件或则文件夹
File创建对象的时候只需要将File代表的文件或者文件见的路径传入构造方法当中 这样创建出来的File对象就知道代表的是哪个文件或文件夹了 如果 创建File对象的时候没有传参数 那么将会报错 因为File没有无参构造 所有的构造方法都是有参构造!
File的构造方法:
File(String pathname) 将一个字符串类型的路径名 转换为抽象路径名 来创建新的File对象
//new File(String pathname) 给定指定的路径的字符串 创建file对象
File file = new File("D:\\JavaItheima\\Java\\JavaSe\\JavaSe进阶\\day11_Stream流&File\\资料");
System.out.println(file);
File(String parent,String child) 从父路径名字符串和子路径字符串进行拼接创建File对象
//new File(String parent,String child) 参数一:文件夹路径 参数二 :文件名
File file1 = new File("D:\\JavaItheima\\Java\\JavaSe\\JavaSe进阶\\day11_Stream流&File\\资料","a.txt");
System.out.println(file1);
File(File parent,String child) 参数一:file对象(符抽象路径名) 参数二 子路径名字符串
//File(File parent,String child) 参数一:file对象(符抽象路径名) 参数二 子路径名字符串
File file2 = new File(file, "a.txt");
System.out.println(file2);
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是否存在
public String getName() 返回由此抽象路径名表示的文件或目录的名称
注:
1、如果调用者是文件 那么获取的是文件名和后缀名
2、如果调用者是一个文件夹 那么获取的是文件夹的名字
public File[] listFiles() 返回由此文件夹下第一层所有的文件和文件夹
注意事项:
注:进入文件夹 获取这个文件夹里面所有的文件和文件夹的File对象 并把这些File对象放在一个数组的返回
1、当调用者是一个路径不存在真实的File对象时 返回的数组为null
2、当调用者是 一个文件时 返回的数组是null
3、 当调用者是一个空文件夹是 返回的数组是一个长度为0 的数组
4、 当调用者是一个有内容的文件夹是 正常返回
5、当调用者是要给需要权限才能进入的文件夹时 但是你的jvm却没有足够的权限 则返回的数组是null
隐藏文件和隐藏文件夹都可以获取
File[] listFiles = src.listFiles();
注:路径中的符号问题
/:单纯的路径符号
\:这个符号的含义为转义 转义当前符号的后一位 想要使用当前路径需要进行再次转换! \\
相对路径与绝对路径:
绝对路径:
什么是绝对路径?
绝对路径是以盘符开始的路径
File file = new File("D:\\JavaItheima\\Java\\JavaSe\\JavaSe进阶\\day11_Stream流&File\\资料");
相对路径:
什么是相对路径?
相对路径在Java中是基于当前项目的路径 在当前项目下开始
File f = new File("../a.txt");
I/O流的分类:
按照流向:
input输入流:从硬盘中读取到内存中
output输出流:从内存中写入到硬盘中
按照功能:
字节流:可以搬运任意的文件类型 并且是原封不动的搬运每一个字节 效率最高
字符流:字符流不是用来搬运的 也不是用来复制文件的 只是用来读取文字 或写出文字的
字节流:
注:字节输出流传输时字节时字节是原封不动的传输 怎么获取的怎么传输 到文件中 但是 在运行结束后 打开文件看到的可看懂的都是经过软件的解码的文件,字节流并没有参与解码!
字节输出流:
字节输出流演示:
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
3、如果写入一个字符串进行输出 可以先把字符串转换位字节数组 <T>.getBytes()
outputStream.write("abc".getBytes());
4、写出换行
\ : z转义
\\ : 取消转义 用来表示目录结构
\t: table 键
\r:return 返回 -- 把光标返回到行首位置
\n:newLine 新一行
5、流对象使用完成后需要调用close方法进行关闭 流对象是内存连接硬盘的通道 这种流通道如果不手动关闭 垃圾回收器是不会进行回收的
finally:确保close方法的执行
finally概述: finally是为了在任何的终端逻辑中 都能够确保close方法的执行
finally注意事项:
finally 关键字 必须要和 try catch一起使用
finally 关键字 不可以单独使用
finally使用格式:
try {
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
outputStream.close();
}
使用finally把close代码包裹:字节输出流异常处理
演化:
第一版:编译报错 局部变量超出作用范围
try {
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally{
fos.close(); //编译报错 fos 只在try的大括号内有效。
}
第二版:编译异常 局部变量使用之前必须先赋值
FileOutputStream fos;
try {
fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally{
fos.close(); //编译报错 fos 是局部变量 局部变量使用之前必须先赋值。 然而 如果 fos的复制出现异常了 fos就没有值了。
}
第三版:编译异常 close有异常
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally{
fos.close(); //编译报错 因为 close方法 有一个编译期异常 所以报红线
}
第四版:运行异常 fos对象可能出现null指针异常
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally{
try{
fos.close(); // 可能有运行时的NullPointerException, 因为 fos初始值是null 如果 fos创建对象不成功,那么fos就是null null调用close方法就报空指针异常
}catch (Exception e){
e.printStackTrace();
}
}
最终使用:
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:\\a.txt");
fos.write(97);
} catch (IOException e) {
e.printStackTrace();
} finally{
if (fos !=null){
try{
fos.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
jdk1.7之后出现了新的io流异常处理方法 为了简化书写的
try (FileOutputStream outputStream = new FileOutputStream("E:\\test\\a.txt");) {
outputStream.write("abc".getBytes());
}catch (IOException e){
e.printStackTrace();
}
字节输入流:
注意事项:
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 Demo03 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./day12_字节流/byteTest/a.txt");
byte[] bytes = new byte[10];
int b;
while ((b = fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,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();
}
}
文件复制:一次读取一个字节数组
public class Demo05 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("E:/a.txt");
FileInputStream fis = new FileInputStream("E:/test/a.txt");
byte[] arr = new byte[1024];
int len ;
while ((len = fis.read(arr))!=-1){
fos.write(arr,0,len);
}
fos.close();
fis.close();
}
}
字节缓冲流:
注:缓冲流只提供缓冲的作用 并不实际去操作数据
构造方法 必须传入一个 具体能够操作文件的流
BufferedInputStream
BufferedOutputStream
字节缓冲流的原理: 底层提供了一个 1024 * 8 长度的字节数组 看是是一次读写一个字节 其实只是把字节存储到了字节数组中 等到数组满了之后在一块写入到文件中
字符流:
为什么要学习字符流?
字节流可以复制、传输任意的文件 因为字节流是把文件原原本本的复制到另一个文件中 编辑器在打开文件的时候自动编码进行展示
但是 在把文本中的文字去读到程序中 对其进行操作时 字节流就有可能做不到 因为 文字 比如说 中文 在不同的编码环境中 里面的编码字节数量就不同 如 GBK环境下 2个字节转换为 1个字符 但是在UTF-8环境下 却是3个字节转换为一个字符! 所以我们就迫切的需要一个可以专门操作字符的流 对文本进行操作!!
字符流的底层:
字节 + 编码表 = 字符!
编码、解码及乱码
编码:编码是字符转换为计算机存储经过的编码规范
解码:解码是将计算机存储的二进制数据转换为我们能看得懂的字符 是经过编码表的查询转换的过程
乱码 :乱码造成的原因是没有编码与解码的格式不一致
多线程
多线程的概念
并行:
多个任务 并列执行(同时执行)
并发:
多个任务之间高速切换(交替执行,谁抢到就是谁的)
进程:
每个应用就是一个进程
线程:
一个应用中 存在多条执行的路径 每个路径就是一个线程 线程是cpu的最小执行单位
多线程的执行原理:
cpu在同一时刻只能执行一条线程 而我们看到的多线程同时执行 其实是 cpu在多线程之间 做着告诉的切换 以至于我们认为是在同时执行
调度方式:
Java中 多线程的执行方式 是抢占式 调度 (随机的)
多线程的实现方式:
继承Thread类:
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);
}
}
}
实现Runnable接口:
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();
}
}
实现Callable<>接口:
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("表白女孩");
}
return "答应";
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<String> task = new FutureTask<>(mc);
Thread thread = new Thread(task);
thread.start();
String s = task.get();
System.out.println(s);
for (int i = 0; i < 100; i++) {
System.out.println();
}
}
}
线程的方法:
设置和获取线程名:
设置线程名
.setName()
获取线程名
.getName()
获取当前线程
Thread.currentThread().getName()
设置线程的优先级
优先级: 1 - 10 默认值:5
.setPriority();
设置守护线程
把第二个线程设置为守护线程
当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
t2.setDaemon(true);
线程数据安全问题:
引起线程安全问题的原因:
1、多个线程操作共享数据
2、多个线程操作共享数据的代码 是分开的 线程可以在执行这些分开的代码之间被其他线程抢掉
方案:
同步代码块:用synchronized关键字包起来的代码。就叫做同步代码块。
synchronized (任意锁对象){
}
线程同步:
线程同步:
概念:如果多个线程在同步代码块上使用的锁对象是 同一个 我们就说这几个线程是同步的
例:
假设 在同步代码块中 使用的同一把锁 obj
两个线程对象为 : t1 t2
synchronized(obj){
代码1 //如果t1线程在代码1这个位置睡眠了, 那么t2 是不能进入代码2执行的。
}
synchronized(obj){
代码2 // t2进不来。
}
同步方法(重点):
非静态的同步方法的锁对象 是this
静态的同步方法的锁对象 是 当前方法所在的类的class文件对象
例:
class Student {
public void show1(){
synchronized(this){
//n行代码 1
}
}
public synchronized void show2(){ //show1 和show2 是同步的
//n行代码 1
}
public static void method1(){
synchronized(Student.class){
//n行代码 2
}
}
public static synchronized void method2(){ // method1 和method2是同步的
//n行代码 2
}
}
Lock接口:
在jdk1.5的时候 出现了一个类 Lock 接口 用于让多个线程实现同步
例:
public class SellTicket implements Runnable {
private int tickets = 100;
//private Object obj = new Object();
private Lock lock = new ReentrantLock(); // 如果想让多个线程 同步。 必须是多个线程用同一个lock
@Override
public void run() {
while (true) {
try{
//synchronized (obj) {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
//}
}finally{
lock.unlock();
}
}
}
}
死锁:
锁的嵌套 容易出现死锁的情况 开发中应当尽量避免锁的嵌套
死锁案例:
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
System.out.println("线程1A"); //第一步: 线程1 执行到此处 被线程2抢到了cpu 此时 objA已经被锁上了。
synchronized (objB){ //第三步:线程1抢到cpu之后 进不去了因为 objB已经被线程2锁上了。只好让出cpu给线程2
System.out.println("线程1B");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
System.out.println("线程2B"); // 第二步: 线程2 进来之后 执行到此处 被线程1抢到了cpu 此时objB被锁上了。
synchronized (objA){ // 第四步:线程2 拿到cpu之后 进不去了因为 objA已经被线程1锁上了。 也只能把cpu让给线程1......就这样让来让去 这就是死锁。
System.out.println("线程2A");
}
}
}
}).start();
线程间通信问题:
生产者和消费者模式:
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
public static boolean flag = false;
//汉堡包的总数量
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
public class Cooker extends Thread {
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(!Desk.flag){
//生产
System.out.println("厨师正在生产汉堡包");
Desk.flag = true;
Desk.lock.notifyAll();
}else{
try {
Desk.lock.wait(); //释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Foodie extends Thread {
@Override
public void run() {
// 1,判断桌子上是否有汉堡包。
// 2,如果没有就等待。
// 3,如果有就开吃
// 4,吃完之后,桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一
//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.flag){
//有
System.out.println("吃货在吃汉堡包");
Desk.flag = false;
Desk.lock.notifyAll(); //
Desk.count--;
}else{
//没有就等待
//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
Foodie f = new Foodie();
Cooker c = new Cooker();
f.start();
c.start();
}
}
线程通信总结:
wait和sleep的区别:
wait():等待之后 会把锁打开
sleep():抱着锁睡觉
wait()和notify()
注意事项:
wait和notify方法 必须 使用锁对象来调用
锁对象是任意对象 任意对象为什么可以调用wait和notify?
因此 wait和notify是定在object中
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(){ //吃货
@Override
public void run() {
while(true){
try {
System.out.println("吃货吃了一个 :" + abq.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true) {
try {
System.out.println("厨师做了一个汉堡包-------------------------------:汉堡包");
abq.put("汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
线程的状态:
Thread.State.TIMED_WAITING :线程状态
NEW :尚未启动的线程处于此状态。
RUNNABLE :在Java虚拟机中执行的线程处于此状态。
BLOCKED :被阻塞等待监视器锁定的线程处于此状态。
WAITING :正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED :已退出的线程处于此状态。
线程池:
创建一个无线大小的线程池(最大不超过int的范围):
public class Demo {
public static void main(String[] args) {
//1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors --- 可以帮助我们创建线程池对象
//ExecutorService --- 可以帮助我们控制线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
}
创建一个指定大小(指定最大范围)的线程池
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来创建线程池:
案例:
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
int y = i;
pool.submit(()-> System.out.println(Thread.currentThread().getName()+"----"+y));
/*
lambda 是 匿名内部类 是 局部的内部类
局部内部类访问局部变量 局部变量之前必须加final修饰 目的是延长生命周期。
jdk8的之后 final可以省略
*/
}
pool.shutdown();
线程池的参数:
参数一:核心线程数量
参数二:最大线程数
参数三:空闲线程最大存活时间
参数四:时间单位
参数五:任务队列
参数六:创建线程工厂
参数七:任务的拒绝策略:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
线程安全问题:
多线程的可见性--volatile 解决
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("程序结束");
}
}
多线程的原子性---原子类解决(底层采用的乐观锁机制)
原子类底层使用的是乐观锁,原子类的乐观锁只是锁住核心代码对数据进行重复检验 但是在原子类外的裸漏数据并没有进行同步检验
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 {
static int a=10; // 比如a在准备阶段的时候 有了存储空间 而且这个空间用0来占位 但是这个10 的赋值是在初始化阶段完成的
}
解析:
如果类中涉及到其他类 类型的 符号引用替换为直接引用
class Student {
String name; // 比如 Student中使用了String 在把Student加载进入内存的时候 要在解析阶段 和String的class文件进行关联。
}
初始化:
给静态变量进行赋值
class Student {
static int a=10; // 比如a在准备阶段的时候赋值为了0 但是在初始化阶段 就把0替换成了10
}
类加载器的分类:
系统类加载器:BootStrapClassLoader
平台类加载器:PlatformClassLoader
应用程序类加载器:AppClassLoader
自定义类加载器
双亲委派机制:
程序定义类的目的是在jvm中使用它,那么为什么要划分出3中类加载器,如果直接设计为一个类加载器不是更加方便吗?
其实这是为了系统安全,而使用的双亲委派机制,双亲委派模式是在Java 1.2后引入的,
其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,
即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?
那么采用这种模式有啥用呢??
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,
而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,
而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?
该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,
将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。
但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常java.lang.SecurityException: Prohibited package name: java.lang
自定义类加载器:
作用:读取网络中传输的class文件 并对其创建对象
类加载器读取配置文件
public class Demo {
public static void main(String[] args)throws Exception {
/*
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 系统类加载器 是专门 加载 自己写的类的
//ClassLoader classLoader = Demo.class.getClassLoader();
InputStream is = systemClassLoader.getResourceAsStream("a.properties");
*/
//FileInputStream is = new FileInputStream("advance33/src/a.properties");
/*
源码 : java文件
字节码文件: class文件
今后你给用户的是class文件 , 一般不给java文件
而给的class文件 那个模块里面 以后是没有src的
*/
//FileInputStream is = new FileInputStream("out/production/advance33/a.properties");
/*
今后你是给他 advance33(模块名)呢 还是给他basic25(项目名)呢? 你给他 advance33
所以今后就没有out/production了
另外用户可能还会把 advance33 改名字。
所以 这个地方 就算你用的是相对路径 你也不能写死 out 写死 production 写死 advance33
*/
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 系统类加载器是专门加载自己写的类的 ,所以他底层可以动态的知道 自己写的类的class文件目录的
InputStream is = systemClassLoader.getResourceAsStream("day16/a.properties");
Properties p = new Properties();
p.load(is);
is.close();
System.out.println(p);
}
}
反射
反射的概述:反射就是另一种创建对象 调用属性、调用方法的方式。(常用于框架中)
为什么说反射是框架的灵魂?
框架:
在框架中 底层大量的使用了反射机制
通过反射可以将框架与自己编写的代码的耦合度降到最低
反射的特点
降低了耦合度 运行时直接执行即可 编译器不需要同时存在!
运行过程中。动态的获取 任意的类 包括里面的 构造方法 成员变量 成员方法。
获取类的class文件
class文件特点
class文件中的成员变量 在java中 Field 类的对象去表示
class文件中的构造方法 在java中 Constructor 类的对象去表示
class文件中的成员方法 在java中 Method 类的对象去表示
获取方式:
1、通过类获取class文件
Class aClass = Student.class;
应用场景:多线程中 静态同步方法的 锁对象 是 当前类的class文件对象。
//通过类名加载
Class className2 = Student.class;
System.out.println(className2);
2、通过对象获取class文件
对象.getClass
应用:equals方法
//通过对象名调用
Student stu = new Student();
Class className3 = stu.getClass();
System.out.println(className3);
3、通过类的名字
Class.forName(类的全类名);
应用:框架中;最常用
//通过class文件获取
Class className1 = Class.forName("test.demo01.Student");
System.out.println(className1);
获取构造方法并使用
获取所有构造方法
.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);
获取有参构造并使用
.getConstructor(String.class, int.class);
System.out.println("————————————有参构造——————————————");
Constructor<?> constructor3 = className.getConstructor(String.class, int.class);
Student stu2 = (Student) constructor3.newInstance("张三", 46);
System.out.println(stu2);
获取私有构造并创建对象并使用
.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(属性名);
System.out.println("—————————————类中公共成员变量———————————————");
Field field2 = className.getField("name");
Student stu = (Student) className.newInstance();
field2.set(stu,"张三");
System.out.println(stu);
获取类中私有的成员变量并使用
.getDeclaredField(属性名);
System.out.println("—————————————类中私有成员变量———————————————");
Field field3 = className.getDeclaredField("age");
field3.setAccessible(true);
field3.set(stu,23);
System.out.println(stu);
获取成员方法
获取类中所有的成员方法
Method[] getMethods()
返回一个包含 方法对象的数组, 方法对象反映由该 Class对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类
Method[] getDeclaredMethods()
返回一个包含 方法对象的数组, 方法对象反映由 Class对象表示的类或接口的所有声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承方法
System.out.println("————————————获取类中所有的成员方法——————————————");
//Method[] methods = className.getMethods();
Method[] methods = className.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
获取类中公共成员方法并使用
Method getMethod?(String name, Class<?>... parameterTypes)
返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法
System.out.println("————————————获取类中公开的成员方法——————————————");
Method method2 = className.getMethod("shou2", String.class);
Student stu = (Student) className.newInstance();
method2.invoke(stu,"张三");
获取类中私有的成员方法并使用
Method getDeclaredMethod?(String name, Class<?>... parameterTypes)
返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 Class对象
System.out.println("————————————获取类中私有的成员方法——————————————");
Constructor<?> constructor = className.getConstructor();
Student stu3 = (Student) constructor.newInstance();
Method method3 = className.getDeclaredMethod("shou3", String.class, int.class);
method3.setAccessible(true);
Object invoke = method3.invoke(stu3, "张三", 44);
System.out.println(invoke);
注解与单元测试
注解:
单元测试:
数据结构与算法:
数据结构
注:数据结构与算法的特点: 拿空间换时间,拿时间换空间
数据在容器中的排列方式,就是数据结构
常见的数据结构:
数组:查询快,增删慢
链表:查询慢,增删快
栈:先进后出
队列:先进先出
红黑树
哈希表
算法:
JavaWeb
MySQL
数据库相关概念
数据库分类
DDL
操作数据库
创建数据库
-- 创建数据库
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 库名;
操作数据表
添加表
-- 添加表
CREATE TABLE 表名(
字段名 数据类型,
字段名2 数据类型,
字段名3 数据类型,
....
)ENGINE=INNODB DEFAULT CHARSET = utf8;
查询表
-- 查询表结构
DESC 表名;
-- 查询表创建的字符集
SHOW CREATE TABLE 表名;
-- 查询当前数据库下所有的表
SHOW TABLES;
修改表
#修改表名
ALTER TABLE 表名 RENAME TO 新的表名;
#修改表的字符集
ALTER TABLE 表名 CHARACTER SET utf8;
#添加列
ALTER TABLE 表名 ADD 列名 数据类型;
#修改列的数据类型
ALTER TABLE 表名 MODIFY 列名 新数据类型;
#修改列名和数据类型
ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
#删除列
ALTER TABLE 表名 DROP 列名;
删除表
# 直接删除表
DROP TABLE 表名;
# 判断删除表
DROP TABLE IF EXISTS 表名;
DML
对表中的数据进行操作
增
#给指定列添加数据
INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…);
给全部列添加数据
INSERT INTO 表名 VALUES(值1,值2,…);
批量添加数据
INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;
删
删除数据
DELETE FROM 表名 [WHERE 条件] ;
-- 删除stu表中所有的数据
delete from stu;
改
#修改表数据:
UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件] ;
练习:
#将张三 的性别改为女
UPDATE db_stu SET gender = '女' WHERE NAME='张三';
#将张三的生日改为 1999-12-12 分数改为99.99
UPDATE db_stu SET score=99.99,birthday='1999-12-12' WHERE NAME = '张三';
#注意:如果update语句没有加where条件,则会将表中所有数据全部修改!
UPDATE db_stu SET gender = '女';
注:
修改语句中如果不加条件,则将所有数据都修改!
像上面的语句中的中括号,表示在写sql语句中可以省略这部分
DQL
基础查询
格式:
查询多个字段
SELECT 字段列表 FROM 表名;
查询所有字段
SELECT * FROM 表名;
去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
起别名
AS: AS 也可以省略
练习:
#查询name、age两列
SELECT NAME,age FROM stu;
#查询所有数据
SELECT * FROM stu;
#查询地址信息
SELECT address FROM stu;
# 去重
select distinct address from stu;
#起别名
select name,math as 数学成绩,english as 英文成绩 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;
#查询年龄等于18岁 或者 年龄等于20岁 或者 年龄等于22岁的学员信息
SELECT * FROM stu WHERE age =18 OR age=20 OR age=22;
SELECT * FROM stu WHERE age IN(18,20,22);
#查询英语成绩为 null的学员信息
SELECT * FROM stu WHERE english = NULL; -- 这条语句是错误的 因为null值不能进行比较使用的
SELECT * FROM stu WHERE english IS NULL;
SELECT * FROM stu WHERE english IS NOT NULL;
#ifnull() 函数的使用 计算每个学员的成绩和
SELECT `name`,age ,sex,math+IFNULL(english,0) AS '总成绩' FROM stu;
模糊查询
_ : 代表了任意的单个字符 % :代表了任意的多个字符
练习:
#查询姓'马'的学员信息
SELECT * FROM stu WHERE `name` LIKE '马%';
#查询第二个字是'花'的学员信息
SELECT * FROM stu WHERE `name` LIKE '_花%';
#查询名字中包含 '德' 的学员信息
SELECT * FROM stu WHERE `name` LIKE '%德%';
排序查询
SELECT 字段列表 FROM 表名 ORDER BY 排序字段名1 [排序方式1],排序字段名2 [排序方式2] …;
练习:
#查询学生信息,按照年龄升序排列
SELECT * FROM stu ORDER BY age; -- 默认的排序就是升序
#查询学生信息,按照数学成绩降序排列
SELECT * FROM stu ORDER BY math DESC;
#查询学生信息,按照数学成绩降序排列,如果数学成绩一样,再按照英语成绩升序排列
SELECT * FROM stu ORDER BY math DESC,english ASC;
注:
ASC : 升序排列 (默认值)
DESC : 降序排列
聚合查询
SELECT 聚合函数名(列名) FROM 表;
练习:
#统计班级一共有多少个学生
SELECT COUNT(id) FROM stu;
SELECT COUNT(*) FROM stu; -- 在统计数值的时候建议使用*来代替使用指定的字段 * 的效率比指定的字段会高
#查询数学成绩的最高分
SELECT MAX(math) FROM stu;
#查询数学成绩的最低分
SELECT MIN(math) FROM stu;
#查询数学成绩的总分
SELECT SUM(math) FROM stu;
#查询数学成绩的平均分
SELECT AVG(math) FROM stu;
#查询英语成绩的最低分
SELECT MIN(IFNULL(english,0)) FROM stu;
注:
null值不参与运算!!
分组查询
SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];
练习:
#查询男同学和女同学各自的数学平均分
SELECT sex , AVG(math) FROM stu GROUP BY sex;
#查询男同学和女同学各自的数学平均分,以及各自人数
SELECT sex,AVG(math),COUNT(*) FROM stu GROUP BY sex;
#查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组
SELECT sex,AVG(math),COUNT(*) FROM stu WHERE math>=70 GROUP BY sex;
#查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组,分组之后人数大于2个的
SELECT sex,AVG(math),COUNT(*) FROM stu WHERE math>70 GROUP BY sex HAVING COUNT(*) >2;
where 和 having 的区别:
* 执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
* 可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。
where是使用与分组之前的 它在select 语句中的优先级高于 group by 的优先级
而having 的优先级是低于group by的优先级的
因此 他们的执行顺序为 先执行where 在group by进行分组 在执行having
注:
分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义
分页查询
SELECT 字段列表 FROM 表名 LIMIT 起始索引 , 查询条目数;
练习:
#从0开始查询,查询3条数据
SELECT * FROM stu LIMIT 0,3;
#每页显示3条数据,查询第1页数据
SELECT * FROM stu LIMIT 0,3;
#每页显示3条数据,查询第2页数据
SELECT * FROM stu LIMIT 3,3;
#每页显示3条数据,查询第3页数据
SELECT * FROM stu LIMIT 6,3;
注:
1、起始索引是从0开始的
2、limit是mysql的方言。
3、 limit起始索引如果为0,则起始索引可以省略。
4、起始索引 = (当前页码 - 1) * 每页显示的条数
DCL
子主题 2
子主题 3
子主题 4
子主题 5
基础框架
Spring
什么是Spring?
Spring是一种轻量级IOC(控制反转)和AOP(面向切面编程)的框架
Spring的好处:
1、简化开发、降低企业级开发的复杂性
2、整合框架、高效整合其他框架,提高企业级应用开发与运行效率
Spring模块
Spring core:核心容器
Spring AOP:Spring面向切面编程
Spring context:Spring上下文
Spring DAO
Spring ORM:对象关系映射模块
Spring Web模块
Spring MVC
IOC与DI
IOC(控制反转)
概念:
IOC---》控制反转,是一种由主动new产生对象转换为接收外部提供对象的一种思想
Bean的三种实例化
1、无参构造方法
<!--构造方法方式:通过对象的无参构造实现将对象加载进ioc容器中-->
<bean id="bookDao" class="com.test.setter.dao.impl.BookDaoImpl"/>
2、静态工厂方法
<!--静态工厂方式:静态工厂方式将对象加载进IOC容器中后,他会随着IOC容器的创建而加载-->
<bean id="staticFactory" class="com.test.factory.StaticFactory" factory-method="getBookDao"/>
3、实例工厂方法
<!--实例化工厂:与静态工厂相比,实例化工厂方法不是静态的 他是随着Bean对象的调用而加载,调用多少次就创建多少次。创建之后不会受IOC容器的管理-->
<bean id="bookService" class="com.test.factory.ImplFactory"/>
<bean id="implFactory" factory-bean="bookService" factory-method="getBookService"/>
Bean的创建与销毁
<!--Bean对象的声明周期:创建与销毁-->
<bean id="bookServiceImpl" class="com.test.setter.service.impl.BookServiceImpl" init-method="init" destroy-method="destroy"/>
DI(依赖注入)
概念:
DI---》依赖注入,它是IOC思想的一种方式,由容器动态将某个依赖关系注入到组件中
两种注入方式:
Setter注入
简单注入
<!--简单注入-->
<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>
构造器注入
简单注入
<!--简单注入-->
<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>
自动装配
集合注入
IOC与DI的关系与区别
Spring的两大核心及DI的作用:
IOC:
Ioc是控制反转,它是一种设计思想
想要了解什么是IOC则需要了解两个问题:
谁控制谁,控制什么?
在传统的JavaSe中 我们习惯了在对象内部通过new的方式区创建对象,是由程序主动的创建所依赖的对象。
而IOC则是有一个专门来创建这些对象的容器,又称为IOC容器。因此是由IOC容器来控制对象的创建。
IOC 控制了对象。
控制了外部资源的获取
为什么会是反转,反转了什么?
在传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,这种方式及为正转。
反转则是不在由我们自己去主动创建依赖的对象,而是转换为了由外部的容器来帮我们进行创建。
为什么是反转? 因为由容器帮我们查找及注入依赖对象 的,对象知识被动的接收依赖的对象
反转了什么? 依赖对象的获取被反转了
IOC的作用:
IOC大大的降低了代码的耦合。
由原本的主动去new来创建出依赖对象的高耦合 转换为了由IOC容器去创建对象 将控制权交给了容器 再由容器进行注入
由主动转换为了被动 对象与对象之间 是松散耦合 程序结构更加的灵活
DI:
DI是依赖注入 它是实现IOC的具体方式
DI的理解也需要从两方面了解
谁依赖谁,为啥要依赖?
对象依赖于IOC容器,因为对象需要IOC容器来提供对象所需要的外部资源
谁注入谁,注入了啥?
将IOC容器注入程序中的某个对象,注入了某个对象所需要的外部资源
总结:
简单来说 当某个对象需要另一个对象的帮助来完成某件事时,再传统的设计中 是由我们主动的去以new的方式创建对象,现在是由Spring中的容器来完成对象的创建。
应用程序再运行时依赖IOC容器来动态注入对象所需要的外部资源 称为DI
AOP:
理解:
简单来说AOP就时将与业务无关但是却被业务模块通用的逻辑封装起来,来降低代码的冗余,同时也便于程序的维护
核心原理:
使用动态代理的设计模式再执行方法前后或异常是加入相关的逻辑
常用方式:
事务处理
权限判断
日志
代理方式:
jdk代理:
CGLB代理:
SpringMVC
SpringBoot
SpringBoot高级:
自动装配原理:
@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包以及自动配置的功能
启动原理:
MyBtais-pulse
Maven高级
0 条评论
下一页