建设网站 费用吗,上海seo关键词优化,学习网站建设多少钱,随县网站建设文章目录 一. 什么是对象1. 抽取属性2. 字段默认值3. this4. 无参构造5. 抽取行为 二. 对象演化1. 对象字段演化2. 对象方法演化3. 贷款计算器 - 对象改造4. 静态变量5. 四种变量 三. 继承1. 继承语法2. 贷款计算器 - 继承改造3. java 类型系统4. 类型转换1) 基本类型转换2) 包… 文章目录 一. 什么是对象1. 抽取属性2. 字段默认值3. this4. 无参构造5. 抽取行为 二. 对象演化1. 对象字段演化2. 对象方法演化3. 贷款计算器 - 对象改造4. 静态变量5. 四种变量 三. 继承1. 继承语法2. 贷款计算器 - 继承改造3. java 类型系统4. 类型转换1) 基本类型转换2) 包装类型转换3) 引用类型转换Java 继承的特点向上向下转型类型判断 4) 其它类型转换 四. 多态1. 何为多态2. 多态前提条件1条件2 3. 多态执行流程4. 贷款计算器 - 多态改造1) 产生方法重写2) 父类型代表子类对象3) 多态好处4) 小结 五. 封装1. 封装差导致的问题2. 加强封装private默认protected 3. JavaBean 六. 接口特性1 - 解决单继承特性2 - 接口多态抽象方法 特性3 - 接口封装 七. Service1. 数据与逻辑分离2. 控制反转3. 依赖注入4. 由Spring创建JavaBean5. 包结构约定 八. 核心类库1. ArrayList数组缺点ArrayList 自动扩容Debug 调试ArrayList 遍历与泛型List 接口 2. HashMap 九. 异常处理1. try - catch2. 继承体系3. Spring 处理异常4. 编译异常与运行时异常5. finally5. finally 一. 什么是对象
什么是对象之前我们讲过对象就是计算机中的虚拟物体。例如 System.outSystem.in 等等。然而要开发自己的应用程序只有这些现成的对象还远远不够。需要我们自己来创建新的对象。
例如我想开发一个电商应用在网上卖手机打算使用对象来代表这些手机。怎么做呢首先要对现实世界的手机进行抽象抽取它属性、抽取它的行为
1. 抽取属性
抽取时要抓取本质属性在真实物体上做简化并不是所有的属性都要抽象 例如对于手机来说分析最终的展示效果可以得知需要品牌、内存、大小、颜色、价格其它页面展示用不上的属性就不必抽取了 这些属性信息在 java 里称为对象的字段根据它们我们就能确定这个对象将来长什么样相当于给对象定义了模板模板一旦确定再创建的对象就不能跳出模板的范围。 模板有了就可以根据它创建各种各样的手机对象比如 手机1品牌苹果、内存128G、大小6.1英寸、颜色星光色、价格5799 可以看到对象的字段要和模板定义的是一模一样的不能多也不能少 手机2品牌红米、内存4G、大小6.53英寸、颜色金色、价格1249 手机3品牌华为、内存4G、大小6.3英寸、颜色幻夜黑、价格999 这个模板以后在 java 里称之为类英文 class这些对象呢英文也要知道object
代码里怎么表示类呢看一下类的语法
class 类 {字段;构造方法(参数) {}返回值类型 方法名(参数) {代码}
}字段定义与之前学过的局部变量、方法参数的定义类似它们本质都是变量 构造方法也是一种方法在将来创建对象时被使用作用是为对象字段赋初始值 构造方法与类同名不用加返回值类型声明 从属于对象的方法不用 static 修饰它就代表对象的行为这里先放一下
就以刚才的手机为例按照语法写一下
public class Phone {// 类型 名字String brand; // 品牌String memory; // 内存String size; // 大小String color; // 颜色double price; // 价格public Phone(String b, String m, String s, String c, double p) {brand b;memory m;size s;color c;price p;}
}有了类才能创建对象创建对象的语法
new 类构造方法(参数)例如
public class TestPhone {public static void main(String[] args) {Phone p1 new Phone(苹果, 128G, 6.1寸, 星光色, 5799.0);Phone p2 new Phone(红米, 4G, 6.53寸, 金色, 1249.0);Phone p3 new Phone(华为, 4G, 6.3寸, 幻夜黑, 999.0);System.out.println(p1.color); // 获取p1的颜色System.out.println(p1.price); // 获取p1的价格p1.price 3000.0; // 修改p1的价格System.out.println(p1.price); // 获取p1的价格}
}前面的变量 p1、p2、p3 分别代表了这三个手机对象也可以理解为给对象起了个名字想知道第一个手机的颜色和价格就使用 p1.color 和 p1.price 来获取想把第一个手机的价格做出优惠就给 p1.price 赋个新值
总结一下
类是对象的模板用字段描述对象将来长什么样用构造给字段变量赋值对象说白了就是一组数据的集合但这些数据不是乱写的有什么不该有什么得按类的规则来
2. 字段默认值
如果字段没有通过构造方法赋值那么字段也会有个默认值
类型默认值说明byte short int long char0float double0.0booleanfalse其它null
比如说想加一个字段 available 表示手机上架还是下架这时就可以使用默认值统一设置
public class Phone {// 类型 名字String brand; // 品牌String memory; // 内存String size; // 大小String color; // 颜色double price; // 价格boolean available; // 是否上架public Phone(String b, String m, String s, String c, double p) {brand b;memory m;size s;color c;price p;}
}构造方法里不对 available 字符赋值默认为下架false要设置为默认上架true可以给字段直接赋值为 true 或在构造方法里给它赋值为 true
3. this
对于变量的命名包括方法参数的命名最好做到见文知义也就是起名的时候起的更有意义最好能一眼看出来这个变量它代表什么你来看手机构造方法这些参数的名字起的好不好不好吧这样改行不行先只改一个 brand
public class Phone {// 类型 名字String brand; // 品牌String memory; // 内存String size; // 大小String color; // 颜色double price; // 价格boolean available; // 是否上架public Phone(String brand, String m, String s, String c, double p) {brand brand;memory m;size s;color c;price p;}
}等号右侧的 brand对应的是方法参数中的 brand 的吧等号左侧的 brand 呢它对应的也是方法参数中的 brand并非对象的 brand 字段
当方法参数与字段重名时需要用在字段前面加 this 来区分
public class Phone {// 类型 名字String brand; // 品牌String memory; // 内存String size; // 大小String color; // 颜色double price; // 价格boolean available; // 是否上架public Phone(String brand, String memory, String size, String color, double price) {this.brand brand;this.memory memory;this.size size;this.color color;this.price price;this.available true;}
}前面有 this 的 brand 对应的是字段的 brand前面没有 this 的 brand 对应的是方法参数中的 brandthis 代表对象自己但只能在构造方法、对象方法中使用没有重名的情况下可以省略 this 提示 如果觉得自己写 this 比较麻烦可以使用 IDEA 的快捷键 ALT Insert 来生成构造方法用 IDEA 生成的构造方法字段和方法参数都是用 this 区分好的 4. 无参构造
带参数的构造并不是必须的也可以使用无参构造例如
public class Student {String name; // 姓名 nullint age; // 年龄 0Student() {}
}使用无参构造创建对象示例如下
public class TestStudent {public static void main(String[] args) {Student s1 new Student();s1.name 张三;s1.age 18;System.out.println(s1.name);System.out.println(s1.age);}
}字段赋值现在不是构造方法内完成了而是先创建了对象再通过【对象.字段】来赋值最终赋值效果是一样的
无参构造有个特性
当 Java 在编译这个类时发现你没有提供任何构造方法它就会给你提供一个无参构造如果你已经提供了带参构造方法Java 就不会主动提供无参构造了
5. 抽取行为
前面我们讲了面向对象编程就是抽象现实世界的物体把它表示为计算机中的对象手机的例子中我们定义了类抽取了字段来描述对象长什么样这节课我们继续来抽取方法描述这个对象的行为方法决定了这个对象能干什么
手机的例子不需要方法计算机中的手机是假的没法打电话下面举一个有方法的例子
这里使用了 javascript 语言来举这个例子虽然大家没有学过这门语言但它的语法与 java 非常类似相信我解释一下大家就能理解另外我待会讲解时咱们把注意力集中的类和方法这部分代码上其它一些 javascript 语言的细节也不用去关注
class Car {constructor(color, speed, x, y) {this.color color; // 颜色this.speed speed; // 速度this.stopped true; // 是否停止this.x x;this.y y;}run() {this.stopped false;}// 更新坐标update() {}// 省略其它无需关注的代码
}class 也是定义一个类这里是一个汽车类页面上能看到根据这个类创建出的一个个汽车对象白的汽车、灰的汽车、黑的汽车constructor 是 js 中的构造方法也是用来给 js 对象的字段赋初值 this.color 是汽车对象的颜色字段this.speed 是汽车对象的速度字段this.stopped 是汽车是否停止this.x 和 this.y 是控制汽车在屏幕上的坐标 坐标以画布的左上角为原点向右是 x 轴正向向下是 y 轴正向x, y 的初始值怎么来的这个我隐藏了大家不用关注 创建汽车对象的语法与 java 几乎是一样的
new Car(red, 5); // 这是创建一个红色的速度为 5 的汽车
new Car(blue, 4); // 这是创建一个蓝色的速度为 4 的汽车执行完上两行创建汽车对象页面效果如下所示 网页上的汽车不能动啊方法要登场了方法就是用来控制对象的行为
主要来看这个 update 方法update 方法的作用被我设计为控制汽车的坐标你只需要把坐标如何变化通过代码在update里写出来就能看到效果
update() {if(this.stopped) {return;}this.y - this.speed;if (this.y 20) {this.y 20;}
}假设只能向上跑每次调用方法时让 this.y 减少减少多少呢 固定成 3但这样大家速度都一样了 改为根据 this.speed 减少 假设顶端是终点this.y 不能超过终点因此加一个 if 判断如果小于 0 则固定为 0 因为汽车的 y 坐标是以矩形底部开始算的0 会导致汽车跑出了画面所以汽车跑到终点时 y 坐标应改为20也就是汽车长度 汽车没有听我命令就一开始就跑了this.stopped 控制这个汽车是停还是移动 在更新坐标里加个判断 this.stopped 为 false 才跑
通过这个例子我们应当知道方法的作用是控制对象的行为
二. 对象演化
更专业的说
对象由数据data和代码code组成 data 有很多称呼fields字段attributes属性properties属性 很多的资料都称为属性但 properties 后面在学 java bean 时会有额外的意义因此我这儿还是称之为字段吧以便后面区分 code 也有多种称呼procedures过程methods方法functions函数我们称之为方法吧
例如
我的需求是对比相同本金不同利率和贷款月份计算总还款额看哪个更划算一些
4.5% 利率借2年6.0% 利率借1年
不用面向对象方式写出来的代码长这样
public class TestCal {public static void main(String[] args) {// 对比相同本金不同利率和贷款月份计算总还款额(等额本息)看哪个更划算一些double p1 100000.0;int m1 24;double yr1 4.5;double r1 cal(p1, m1, yr1);System.out.println(4.5% 利率借 2 年 r1);double p2 100000.0;int m2 12; // 1 年double yr2 6.0;double r2 cal(p2, m2, yr2);System.out.println(6.0% 利率借 1 年 r2);}static double cal(double p, int m, double yr) {double mr yr / 100.0 / 12;double pow Math.pow(1 mr, m);return m * p * mr * pow / (pow - 1);}
}1. 对象字段演化
那么将来对象从何而来呢很简单找关系把一组相关的数据作为一个整体就形成了对象。
我们现有的数据中哪些是相关的
4.5、24、100000.0 这些数据为一组封装成 Calculator c1 对象6.0、12、100000.0 这些数据为一组封装成 Calculator c2 对象
代码变成了下面的样子
public class Cal {double p;int m;double yr;public Cal(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}
}public class TestCal {public static void main(String[] args) {// 对比相同本金不同利率和贷款月份计算总还款额(等额本息)看哪个更划算一些Cal c1 new Cal(100000.0, 24, 4.5);double r1 cal(c1);System.out.println(4.5% 利率借 2 年 r1);Cal c2 new Cal(100000.0, 12, 6.0);double r2 cal(c2);System.out.println(6.0% 利率借 1 年 r2);}static double cal(Cal c) {double mr c.yr / 100.0 / 12;double pow Math.pow(1 mr, c.m);return c.m * c.p * mr * pow / (pow - 1);}
}计算时把这里的 c1, c2 传递过去。方法也变成了只需要一个 Calculator 参数因为它一个参数能顶原来三个参数方法内部需要的数据来自于对象的字段。注意方法此时还是用的 static 方法方法这别着急马上讲
总结把相关的数据作为一个整体就形成了对象对象的字段演化完毕
2. 对象方法演化
方法执行总得需要一些数据以前我们学习的主要是这种 static 方法它的数据全部来自于方法参数。
今天开始要学习对象方法顾名思义它是从属于对象的方法语法上要去掉 static变成这个样子
public class Cal {double p;int m;double yr;public Cal(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}double cal() {double mr yr / 100.0 / 12;double pow Math.pow(1 mr, m);return m * p * mr * pow / (pow - 1);}
}看看改动成对象方法后都有哪些代码发生了变化为啥不需要参数了呢
这种对象方法执行需要的数据
一部分来自于方法参数另一部分来自于方法从属的对象
既然我们讲的这种对象方法都从属于 Calculator 对象了那么方法参数这里是不是就没必要再加一个 Calculator 对象了啊
方法体内这些本金、月份、利率都来自于方法所从属的对象的字段。不用写前面的 c. 了
当然我们这个例子中本金、月份、利率在方法所从属的那个 Calculator 对象中已全部包含因此方法上也无需更多其它参数。如果方法执行的有些数据对象未包含那你还是得添加方法参数
最后方法调用时为了表达与对象的这种从属关系格式也应变化为对象.方法()
public class TestCal {public static void main(String[] args) {// 对比相同本金不同利率和贷款月份计算总还款额(等额本息)看哪个更划算一些Cal c1 new Cal(100000.0, 24, 4.5);double r1 c1.cal();System.out.println(4.5% 利率借 2 年 r1);Cal c2 new Cal(100000.0, 12, 6.0);double r2 c2.cal();System.out.println(6.0% 利率借 1 年 r2);}
}例如
c1.cal() 执行时cal 方法就知道我执行需要的 ymyr 这些数据来自于 c1 对象c2.cal() 执行时cal 方法就知道我执行需要的 ymyr 这些数据来自于 c2 对象
对象的方法演化完毕 静态方法 vs 对象方法 而 static 方法需要的数据全都来自于方法参数它没有关联对象没有对象的那一部分数据对象方法执行的数据一部分数据从方法参数转移至对象内部 3. 贷款计算器 - 对象改造
用面向对象思想设计等额本息和等额本金两个类
class Calculator0 {Calculator0(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}double p;int m;double yr; String[] cal0() {double mr yr / 12 / 100.0;double pow Math.pow(1 mr, m);double payment p * mr * pow / (pow - 1);return new String[]{NumberFormat.getCurrencyInstance().format(payment * m),NumberFormat.getCurrencyInstance().format(payment * m - p)};}String[][] details0() {String[][] a2 new String[m][];double mr yr / 12 / 100.0;double pow Math.pow(1 mr, m);double payment p * mr * pow / (pow - 1); // 月供for (int i 0; i m; i) {double payInterest p * mr; // 偿还利息double payPrincipal payment - payInterest; // 偿还本金p - payPrincipal; // 剩余本金String[] row new String[]{ // 一行的数据(i 1) ,NumberFormat.getCurrencyInstance().format(payment),NumberFormat.getCurrencyInstance().format(payPrincipal),NumberFormat.getCurrencyInstance().format(payInterest),NumberFormat.getCurrencyInstance().format(p)};a2[i] row;}return a2;}
}class Calculator1 {Calculator1(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}double p;int m;double yr;String[] cal1() {double payPrincipal p / m; // 偿还本金double backup p; // 备份本金double mr yr / 12 / 100.0;double payInterestTotal 0.0; // 总利息for (int i 0; i m; i) {double payInterest p * mr; // 偿还利息p - payPrincipal; // 剩余本金payInterestTotal payInterest;}// [0]还款总额 [1]总利息return new String[]{NumberFormat.getCurrencyInstance().format(backup payInterestTotal),NumberFormat.getCurrencyInstance().format(payInterestTotal)};}String[][] details1() {double payPrincipal p / m; // 偿还本金double mr yr / 12 / 100.0;String[][] a2 new String[m][];for (int i 0; i m; i) {double payInterest p * mr; // 偿还利息p - payPrincipal; // 剩余本金double payment payPrincipal payInterest; // 月供String[] row new String[]{(i 1) ,NumberFormat.getCurrencyInstance().format(payment),NumberFormat.getCurrencyInstance().format(payPrincipal),NumberFormat.getCurrencyInstance().format(payInterest),NumberFormat.getCurrencyInstance().format(p)};a2[i] row;}return a2;}
}控制器代码则变为比以前看着也简洁了不少
对象封装了数据方法封装了计算逻辑封装这是面向对象的好处之一面向对象编程并不能做出更强大的功能只是改变的代码的结构
Controller
public class CalController {RequestMapping(/cal)ResponseBodyString[] cal(double p, int m, double yr, int type) {if (type 0) { // 等额本息return new Calculator0(p, m, yr).cal0();} else { // 等额本金return new Calculator1(p, m, yr).cal1();}}RequestMapping(/details)ResponseBodyString[][] details(double p, int m, double yr, int type) {if (type 0) {return new Calculator0(p, m, yr).details0();} else {return new Calculator1(p, m, yr).details1();}}
}4. 静态变量
下面是对圆、计算圆面积进行了面向对象设计
public class Circle {double r; // 半径double pi 3.14;public Circle(double r) {this.r r;}double area() {return pi * r * r;}
}测试代码如下
public class TestCircle {public static void main(String[] args) {Circle c1 new Circle(1.0);c1.pi 3.14;Circle c2 new Circle(1.0);c2.pi 3;System.out.println(c1.area()); // 圆的面积计算结果为 3.14System.out.println(c2.area()); // 圆的面积计算结果为 3}
}这显然不合理不能一个圆计算时 π \pi π 值是 3.14换成另一个圆计算时 π \pi π 值就变成了 3对于 π \pi π 值来讲应该是所有圆共享一个值
你看对于将来千千万万个圆对象来说它们各有各的半径但无论圆有多少个3.14 这个数只需要有一个就足够了
改进如下
public class Circle {double r; // 半径static double pi 3.14; // 静态变量, 所有圆对象共享它public Circle(double r) {this.r r;}double area() {return pi * r * r;}
}回到测试代码
public class TestCircle {public static void main(String[] args) {Circle c1 new Circle(1.0);c1.pi 3.14;Circle c2 new Circle(1.0);c2.pi 3;System.out.println(c1.area()); // 圆的面积计算结果为 3System.out.println(c2.area()); // 圆的面积计算结果为 3}
}两次计算结果相同因为 c1.pi 和 c2.pi 都是修改的同一个变量。注意几点
静态变量虽然也能通过对象来使用但建议通过类名类使用例如上例中应该这么写Circle.pi 3如果不希望 pi 的值改来改去可以再加一个 final 修饰符static final pi 3.14 final 加在变量上表示该变量只能赋值一次之后就不能修改了 最后要知道计算如果需要更为精确的 pi 值可以用 Math.PI 上面我们自己写的 pi 只是为了举例需要
5. 四种变量
至今为止一共学习了四种变量下面就对它们做一个简单对比
public class TestVariable {public static void main(String[] args) {m(10);if (true) {C c1 new C(30); // 出了if语句块,c1 对象就无法使用了随带的它内部的对象变量也失效}}static void m(int a) { // 1. 参数变量, 作用范围是从方法调用开始直到方法调用结束for (int i 0; i 10; i) {int b 20; // 2. 局部变量, 作用范围从定义开始到包围它的 } 为止, 必须赋初值才能使用System.out.println(b);}}
}class C {int c; // 3. 对象变量(成员变量) 从对象创建开始, 到对象不能使用为止public C(int c) {this.c c;}static int d 40; // 4. 静态变量, 从类加载开始, 到类卸载为止
}方法参数的作用范围就是从方法调用开始直到这个方法结束为止这是参数变量的作用范围局部变量的作用范围要更小一些它是从局部变量定义开始到包围它的 } 为止 局部变量跟其它几个变量有个不一样的地方它需要先赋值再使用否则会报错 对象变量其实就是对象中的字段的作用范围是从对象创建开始到对象不能使用为止它的作用范围和对象是一样的 对象变量是每个对象私有的 静态变量它的作用范围从类加载开始到类卸载为止 第一次用到这个类时会把类的字节码加载到虚拟机里这称之为类加载某个类以后不会再用到类就会从虚拟机中卸载静态变量是所有对象共享的
三. 继承
1. 继承语法
阅读代码发现这两个类中有一些相同的对象变量和方法代码能否减少这两个类的重复代码答案是继承
继承的语法
class 父类 {字段;方法() {}
}class 子类 extends 父类 {}可以用父子类继承的方式减少重复声明例如 A 是父类BC 类是子类那么
class A {String name;void test() {}
}class B extends A {
}class C extends A {
}子类能从父类中继承它所声明的字段、方法
但注意构造方法不能继承
给父类加一个带参构造发现子类都报错了为什么呢一方面构造方法不能继承另一方面对子类来说你得调用父类的带参构造给父类的 name 字段赋值吧子类得先创建自己的构造方法然后用 super 调用父类的带参构造
class A {String name;A(String name) {this.name name;}void test() {}
}class B extends A {B(String name) {super(name);}
}class C extends A {C(String name) {super(name);}
}2. 贷款计算器 - 继承改造
回到我们的例子
第一步减少重复的字段声明定义一个父类型里面放 p、m、yr 这三个字段
public class Calculator {double p;int m;double yr;Calculator(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}
}然后子类中不必再写这三个字段
class Calculator0 extends Calculator{Calculator0(double p, int m, double yr) {super(p, m, yr);}// ...
}class Calculator1 extends Calculator{Calculator1(double p, int m, double yr) {super(p, m, yr);}// ...
}第二步分析哪些代码重复
可以看到详情中生成一行数据的代码重复了抽取为父类的方法然后子类可以重用
public class Calculator {// ...String[] createRow(double payment, int i, double payInterest, double payPrincipal) {return new String[]{ // 一行的数据(i 1) ,NumberFormat.getCurrencyInstance().format(payment),NumberFormat.getCurrencyInstance().format(payPrincipal),NumberFormat.getCurrencyInstance().format(payInterest),NumberFormat.getCurrencyInstance().format(p)};}
}例如
public class Calculator0 extends Calculator {// ...OverrideString[][] details() {String[][] a2 new String[m][];double mr yr / 12 / 100.0;double pow Math.pow(1 mr, m);double payment p * mr * pow / (pow - 1); // 月供for (int i 0; i m; i) {double payInterest p * mr; // 偿还利息double payPrincipal payment - payInterest; // 偿还本金p - payPrincipal; // 剩余本金// 这里重用了从父类继承的方法a2[i] createRow(i, payment, payPrincipal, payInterest);}return a2;}
}继承能够减少字段定义和方法定义的重复代码
不重复意味着代码的可维护性提高重复意味着一处修改凡是重复的地方都要跟着修改
3. java 类型系统
java 中的类型分成了两大类 基本类型 primitive type 整数 byte short int long小数 float double字符 char布尔 boolean 引用类型 reference type 除了基本类型以外的其它类型都属于引用类型引用类型可以看成是由基本类型组成的复杂类型例如 String 内部由 byte[] 组成而 byte[] 又是由 byte 组成Phone 内部价格是 double品牌等都是 String 包装类型 每个基本类型都有与之对应的包装类型见下表包装类型是对象既然是对象就可以有对象的特征如字段、方法、继承 … null 值不能对 null 值进一步使用使用字段、调用方法例如 String str null;str.length() 求字符串长度时就会出现 NullPointerException 空指针异常使用引用类型之前最好做非空判断例如if(str ! null) 再做进一步操作
包装类型基本类型备注BytebyteShortshortIntegerintLonglongFloatfloatDoubledoubleCharactercharBooleanboolean
4. 类型转换
1) 基本类型转换 #mermaid-svg-mFhSSiG6D9dv20ll {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .error-icon{fill:#552222;}#mermaid-svg-mFhSSiG6D9dv20ll .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mFhSSiG6D9dv20ll .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mFhSSiG6D9dv20ll .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mFhSSiG6D9dv20ll .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mFhSSiG6D9dv20ll .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mFhSSiG6D9dv20ll .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mFhSSiG6D9dv20ll .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mFhSSiG6D9dv20ll .marker.cross{stroke:#333333;}#mermaid-svg-mFhSSiG6D9dv20ll svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mFhSSiG6D9dv20ll .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .cluster-label text{fill:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .cluster-label span{color:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .label text,#mermaid-svg-mFhSSiG6D9dv20ll span{fill:#333;color:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .node rect,#mermaid-svg-mFhSSiG6D9dv20ll .node circle,#mermaid-svg-mFhSSiG6D9dv20ll .node ellipse,#mermaid-svg-mFhSSiG6D9dv20ll .node polygon,#mermaid-svg-mFhSSiG6D9dv20ll .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mFhSSiG6D9dv20ll .node .label{text-align:center;}#mermaid-svg-mFhSSiG6D9dv20ll .node.clickable{cursor:pointer;}#mermaid-svg-mFhSSiG6D9dv20ll .arrowheadPath{fill:#333333;}#mermaid-svg-mFhSSiG6D9dv20ll .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mFhSSiG6D9dv20ll .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mFhSSiG6D9dv20ll .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-mFhSSiG6D9dv20ll .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-mFhSSiG6D9dv20ll .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mFhSSiG6D9dv20ll .cluster text{fill:#333;}#mermaid-svg-mFhSSiG6D9dv20ll .cluster span{color:#333;}#mermaid-svg-mFhSSiG6D9dv20ll div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mFhSSiG6D9dv20ll :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} double float long int short char byte 整数和 char 7种类型 顺箭头方向隐式转换逆箭头方向需要显式强制转换可能会损失精度 boolean 类型无法与其它基本类型转换short 与 char 也不能转换
隐式转换
byte a 10;
int b a; // 自动从 byte 转换为 int强制转换
int c 20;
byte d (byte) c; // 在圆括号内加上要转换的目标类型强制转换可能损失精度
int c 1000;
byte d (byte) c; // byte 的数字范围就是在 -128 ~ 127存不下 1000最后结果是 -242) 包装类型转换 #mermaid-svg-8vqKj1l5Wo1r490i {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .error-icon{fill:#552222;}#mermaid-svg-8vqKj1l5Wo1r490i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8vqKj1l5Wo1r490i .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8vqKj1l5Wo1r490i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8vqKj1l5Wo1r490i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8vqKj1l5Wo1r490i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8vqKj1l5Wo1r490i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8vqKj1l5Wo1r490i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8vqKj1l5Wo1r490i .marker.cross{stroke:#333333;}#mermaid-svg-8vqKj1l5Wo1r490i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8vqKj1l5Wo1r490i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .cluster-label text{fill:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .cluster-label span{color:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .label text,#mermaid-svg-8vqKj1l5Wo1r490i span{fill:#333;color:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .node rect,#mermaid-svg-8vqKj1l5Wo1r490i .node circle,#mermaid-svg-8vqKj1l5Wo1r490i .node ellipse,#mermaid-svg-8vqKj1l5Wo1r490i .node polygon,#mermaid-svg-8vqKj1l5Wo1r490i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8vqKj1l5Wo1r490i .node .label{text-align:center;}#mermaid-svg-8vqKj1l5Wo1r490i .node.clickable{cursor:pointer;}#mermaid-svg-8vqKj1l5Wo1r490i .arrowheadPath{fill:#333333;}#mermaid-svg-8vqKj1l5Wo1r490i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8vqKj1l5Wo1r490i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8vqKj1l5Wo1r490i .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-8vqKj1l5Wo1r490i .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-8vqKj1l5Wo1r490i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8vqKj1l5Wo1r490i .cluster text{fill:#333;}#mermaid-svg-8vqKj1l5Wo1r490i .cluster span{color:#333;}#mermaid-svg-8vqKj1l5Wo1r490i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8vqKj1l5Wo1r490i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 8 7 6 5 4 3 2 1 Boolean boolean Character char Double double Float float Long long Integer int Short short Byte byte 包装类型和它对应的基本类型之间可以自动转换如
int a 20;
Integer b a;Integer c new Integer(30);
int d c;3) 引用类型转换
Java 继承的特点
单继承子类只能继承一个父类Object 是所有其它类型直接或间接的父类型定义 class 时不写 extends 这个类也是继承自 Object子类与父类、祖先类之间可以用【是一个 is a】的关系来表达
向上向下转型 #mermaid-svg-0BPx0uprvY8gQ2UH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .error-icon{fill:#552222;}#mermaid-svg-0BPx0uprvY8gQ2UH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0BPx0uprvY8gQ2UH .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-0BPx0uprvY8gQ2UH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0BPx0uprvY8gQ2UH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0BPx0uprvY8gQ2UH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0BPx0uprvY8gQ2UH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0BPx0uprvY8gQ2UH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0BPx0uprvY8gQ2UH .marker.cross{stroke:#333333;}#mermaid-svg-0BPx0uprvY8gQ2UH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0BPx0uprvY8gQ2UH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .cluster-label text{fill:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .cluster-label span{color:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .label text,#mermaid-svg-0BPx0uprvY8gQ2UH span{fill:#333;color:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .node rect,#mermaid-svg-0BPx0uprvY8gQ2UH .node circle,#mermaid-svg-0BPx0uprvY8gQ2UH .node ellipse,#mermaid-svg-0BPx0uprvY8gQ2UH .node polygon,#mermaid-svg-0BPx0uprvY8gQ2UH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0BPx0uprvY8gQ2UH .node .label{text-align:center;}#mermaid-svg-0BPx0uprvY8gQ2UH .node.clickable{cursor:pointer;}#mermaid-svg-0BPx0uprvY8gQ2UH .arrowheadPath{fill:#333333;}#mermaid-svg-0BPx0uprvY8gQ2UH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0BPx0uprvY8gQ2UH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0BPx0uprvY8gQ2UH .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-0BPx0uprvY8gQ2UH .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-0BPx0uprvY8gQ2UH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0BPx0uprvY8gQ2UH .cluster text{fill:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH .cluster span{color:#333;}#mermaid-svg-0BPx0uprvY8gQ2UH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0BPx0uprvY8gQ2UH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Object 家电 动物 电视 冰箱 猫 狗 顺箭头方向向上隐式转换即子类可以用它的更高层的类型代表表达一种是一个的关系 例如一个猫对象可以隐式转换为动物 Animal a new Cat(); // 用父类型的变量 a 代表了一只猫对象
Object b new Cat(); // 用祖先类型的变量 b 代表了一只猫对象逆箭头方向向下首先要符合是一个规则然后用显式强制转换 如果一个动物变量它代表的是一个猫对象可以通过强制转换还原成猫 Animal a new Cat();
Cat c (Cat) a;如果一个动物变量它代表的是一个猫对象即使强制转换也不能变成狗编译不报错但运行就会出现 ClassCastException Animal a new Cat();
Dog d (Dog) a;为什么需要向上转型主要是为了使用父类统一处理子类型
例1
static void test(Animal a) {}这时此方法既可以处理猫对象也可以处理狗对象
test(new Cat());
test(new Dog());例2用父类型的数组可以既装猫对象也装狗对象
Animal[] as new Animal[]{ new Cat(), new Dog() };类型判断
Animal a new Cat();如果想知道变量 a 代表对象的实际类型可以使用
System.out.println(a.getClass()); // 输出结果 class com.itheima.CatgetClass() 是对象从 Object 类中继承的方法
如果想检查某个对象和类型之间是否符合【是一个】的关系可以使用
Animals a new Cat();
Object b new Cat();System.out.println(a instanceof Cat); // true
System.out.println(a instanceof Dog); // false
System.out.println(b instanceof Animal);// true经常用在向下转型之前符合是一个的关系再做强制类型转换
4) 其它类型转换
除了以上转换规则在赋值、方法调用时一旦发现类型不一致都会提示编译错误需要使用一些转换方法才行
例如两个字符串对象要转成整数做加法
String a 1;
String b 2;System.out.println(a b); // 这样不行字符串相加结果会拼接为 12
System.out.println(Integer.parseInt(a) Integer.parseInt(b)); // 转换后再相加结果是 3四. 多态
1. 何为多态
例如我这里有多个汽车对象调用这些汽车对象的 update 方法修改坐标表面调用的方法都一样但实际的效果是它们各自的运动轨迹不同
function draw() {for (let i 0; i cars.length; i) {let c cars[i];c.update(); // update 方法用来修改坐标c.display();}
}cars 是个汽车数组里面放了3个汽车对象 再比如我有一个 getAnimals() 方法会传递过来 Animal 动物数组但遍历执行 Animal 的方法 say行为不同
public class TestAnimal {public static void main(String[] args) {Animal[] animals getAnimals();for (int i 0; i animals.length; i) {Animal a animals[i];a.say();}}
}会输出
喵~
汪汪汪
哼哧哼哧如果像上两个例子中体现的同一个方法在执行时表现出了不同的行为称这个方法有多态的特性。
2. 多态前提
不是所有方法都有多态像之前写过的静态方法不管怎么调用表现出的行为都是一样的。那么要成为这种多态方法要满足哪些条件呢先来看看多态这个词是怎么来的
多态英文 polymorphism 本意是多种形态是指执行一个相同的方法最终效果不同。为什么会有这种效果
方法虽然都是同一个但调用它们的对象相同吗看起来都是 Animal 啊其实不是
之前讲向上转型时讲过子类对象可以向上转型用父类型代表它这里的每个 Animal a 只是代表右侧对象右侧的实际对象是一些子类对象大家应该都猜出来了分别是猫、狗、猪这种有多态特性的方法在调用时会根据实际的对象类型不同而行为不同而正是每个子类对象中的 say 方法写的不一样从而表现出不同的叫声
方法具备多态性的两个条件
条件1
用父类型代表子类对象有了父类型才能代表多种子类型只有子类型自己那将来有多种可能吗不行吧
比如说无论获取数组还是遍历都使用猪类型那最终也只能发出猪叫声这是第一个前提条件
条件2
第二个条件是子类和父类得有一个相同的 say 方法。如果子类存在与父类相同的方法称发生了方法重写。重写方法要满足
方法名和参数都完全相同对象方法才能重写检查是否发生了重写可以用 Override 注解它可以帮我们检查子类方法是否符合重写规则
只有重写了才能表现出多种形态如果没有重写调用的都是父类方法最终的效果是相同的没有多态了
3. 多态执行流程
表面上调用的是 Animal 父类的 say 方法实际内部会执行下面的逻辑判断 #mermaid-svg-B67nLhYxBi4XFFZP {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .error-icon{fill:#552222;}#mermaid-svg-B67nLhYxBi4XFFZP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B67nLhYxBi4XFFZP .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-B67nLhYxBi4XFFZP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B67nLhYxBi4XFFZP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B67nLhYxBi4XFFZP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B67nLhYxBi4XFFZP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B67nLhYxBi4XFFZP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B67nLhYxBi4XFFZP .marker.cross{stroke:#333333;}#mermaid-svg-B67nLhYxBi4XFFZP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B67nLhYxBi4XFFZP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .cluster-label text{fill:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .cluster-label span{color:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .label text,#mermaid-svg-B67nLhYxBi4XFFZP span{fill:#333;color:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .node rect,#mermaid-svg-B67nLhYxBi4XFFZP .node circle,#mermaid-svg-B67nLhYxBi4XFFZP .node ellipse,#mermaid-svg-B67nLhYxBi4XFFZP .node polygon,#mermaid-svg-B67nLhYxBi4XFFZP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B67nLhYxBi4XFFZP .node .label{text-align:center;}#mermaid-svg-B67nLhYxBi4XFFZP .node.clickable{cursor:pointer;}#mermaid-svg-B67nLhYxBi4XFFZP .arrowheadPath{fill:#333333;}#mermaid-svg-B67nLhYxBi4XFFZP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B67nLhYxBi4XFFZP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B67nLhYxBi4XFFZP .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-B67nLhYxBi4XFFZP .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-B67nLhYxBi4XFFZP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B67nLhYxBi4XFFZP .cluster text{fill:#333;}#mermaid-svg-B67nLhYxBi4XFFZP .cluster span{color:#333;}#mermaid-svg-B67nLhYxBi4XFFZP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B67nLhYxBi4XFFZP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} yes no yes no yes no yes Animal a a.say() Animal.say() Dog.say() Cat.say() Pig.say() is Animal? is Dog? say() Overried? is Cat? say() Overried? is Pig? say() Overried? 循环第一次的 Animal a 代表的是一只猫对象就会走第三个分支 继续去看看看猫里面有没有 Override say() 方法 因为我们重写了 say() 方法最终就执行的是 Cat.say() 方法那如果啊我的猫里面没有重写这个方法。最终执行的就是 Animal.say() 方法 循环第二次的 Animal a 代表的是一只狗对象就会走第二个分支 继续去看看看狗里面有没有 Override say() 方法 因为我们重写了 say() 方法最终就执行的是 Dog.say() 方法 循环第三次的 Animal a 代表的是一只猪对象就会走第四个分支 继续去看看看猪里面有没有 Override say() 方法 因为我们重写了 say() 方法最终就执行的是 Pig.say() 方法
总结一下具有这种多态特性的方法调用内部就会走这样很多的判断逻辑当然这些判断是 JVM 虚拟机帮我们判断的不需要我们自己判断。简单的说多态方法调用时得先看变量所代表的对象真正类型是什么。是狗走狗的逻辑是猫走猫的逻辑。然后呢优先执行真正类型中的重写方法。如果没有重写方法呢才执行父类中的方法。这就是这种多态方法执行的流程。
伪代码如下
Animal a ...
Class c a.getClass() // 获得对象实际类型
if(c Animal.class) {执行 Animal.say()
} else if(c Dog.class Dog 重写了 say 方法) {执行 Dog.say()
} else if(c Cat.class Cat 重写了 say 方法) {执行 Cat.say()
} else if(c Pig.class Pig 重写了 say 方法) {执行 Pig.say()
} else {执行 Animal.say()
}4. 贷款计算器 - 多态改造
在控制器代码中需要用 if else 来判断使用哪个 Calculator 对象完成计算Calculator0 还是 Calculator1将来如果贷款类型越来越多就要写好多 if else如何避免呢利用多态的原理让 jvm 帮我们做判断
Animal a … 利用多态a 如果代表的是狗对象走狗的逻辑代表的是猫对象走猫的逻辑Calculator c … 利用多态c 如果代表的是等额本息对象走等额本息逻辑代表的是等额本金对象走等额本金的计算逻辑
1) 产生方法重写
原来的
Calculator0 的方法叫 cal0 和 details0Calculator1 的方法叫 cal1 和 details1Calculator 父类中没有这俩方法
显然不行
改写如下
class Calculator {// ...String[] cal() {return null;}String[][] details() {return null;}
}class Calculator0 extends Calculator {Calculator0(double p, int m, double yr) {super(p, m, yr);}OverrideString[] cal() {// ...}OverrideString[][] details() {// ...}}class Calculator1 extends Calculator {Calculator1(double p, int m, double yr) {super(p, m, yr);}OverrideString[] cal() {// ...}OverrideString[][] details() {// ...}}2) 父类型代表子类对象
根据类型创建不同 Calculator 对象有点小技巧避免了创建对象时的 if else如下
Calculator[] getCalculator(double p, int m, double yr) {return new Calculator[] {new Calculator0(p, m, yr),new Calculator1(p, m, yr)};
}最后通过父类型来执行表面上是调用 Calculator 父类的 cal() 和 details() 方法但实际执行的是某个子类的 cal() 和 details() 方法通过多态避免了方法调用时的 if else 判断
Controller
public class CalController {Calculator[] getCalculator(double p, int m, double yr) {return new Calculator[] {new Calculator0(p, m, yr),new Calculator1(p, m, yr)};}RequestMapping(/cal)ResponseBodyString[] cal(double p, int m, double yr, int type) {Calculator[] cs getCalculator(p, m, yr);return cs[type].cal();}RequestMapping(/details)ResponseBodyString[][] details(double p, int m, double yr, int type) {Calculator[] cs getCalculator(p, m, yr);return cs[type].details();}}cs[type] 是根据类型找到对应的子类对象例如
type 0 时其实是获取了数组中索引 0 的对象即 new Calculator0(p, m, yr)type 1 时其实是获取了数组中索引 1 的对象即 new Calculator1(p, m, yr)
3) 多态好处
多态有什么好处呢
比如现在想再加一种贷款计算方式type 2无论借多少钱多少个月利息总为 0
新增一个 Calculator 子类
public class Calculator2 extends Calculator{Calculator2(double p, int m, double yr) {super(p, m, yr);}OverrideString[] cal() {return new String[]{NumberFormat.getCurrencyInstance().format(p),NumberFormat.getCurrencyInstance().format(0)};}OverrideString[][] details() {String[][] a2 new String[m][];double payment p / m;for (int i 0; i m; i) { p - payment;a2[i] createRow(payment, i, 0, payment);}return a2;}
}原有代码只需很少改动扩展性高了
对于原有的这两个方法来讲它需要关心你到底是哪个子类对象吗它不需要关心因为对于它来讲它都是统一按照父类型来处理的通过父类型多态调用方法具体该调哪个子类方法多态内部就处理好了
Controller
public class CalController {Calculator[] getCalculator(double p, int m, double yr) {return new Calculator[] {new Calculator0(p, m, yr),new Calculator1(p, m, yr),new Calculator2(p, m, yr)};}RequestMapping(/cal)ResponseBodyString[] cal(double p, int m, double yr, int type) {Calculator[] cs getCalculator(p, m, yr);return cs[type].cal();}RequestMapping(/details)ResponseBodyString[][] details(double p, int m, double yr, int type) {Calculator[] cs getCalculator(p, m, yr);return cs[type].cal();}}可以尝试用原来 if else 的办法自己实现一遍对比一下代码量。
4) 小结
关于多态的应用的例子讲完了总结一下
前提
java 中的类型系统允许用父类型代表子类型对象这是多态前提之一子类和父类之间发生了方法重写这是多态前提之二
效果
调用父类型的方法可能会有不同的行为取决于该方法是否发生了重写
什么时候使用多态
多态能够用一个父类型统一操作子类对象原本要根据类型做判断写很多 if else 的地方都可以考虑使用多态来消除 if else提高扩展性
五. 封装
1. 封装差导致的问题
封装的例子我们前面已经见过一些了
对象字段封装了数据方法封装了计算逻辑对外隐藏了实现细节但看看下面的 js 例子直接修改对象的字段会产生不合理的效果 cars[0].y 20 就会导致汽车瞬移本来我的规则是通过 update 方法一次按 speed 速度移动一点现在直接修改字段突破了方法的限制
class Car {constructor(color, speed, x, y) {this.color color; // 颜色this.speed speed; // 速度this.stopped true; // 是否停止this.x x;this.y y;}run() {this.stopped false;}update() {if(this.stopped) {return;}this.y - this.speed;if( this.y 20) {this.y 20;}}display() {fill(this.color);rect(this.x, this.y, 10, -20);}
}其根本问题在于使用者直接使用了字段绕过了 update 方法对 y 值的处理解决方法也很简单就是将字段的访问权限设置为私有字段定义时前面加 # 即可其它用 xy 的地方也替换为 #x 和 #y
class Car {#x; // 设置为私有#y; // 设置为私有constructor(color, speed, x, y) {this.color color; // 颜色this.speed speed; // 速度this.stopped true; // 是否停止this.#x x;this.#y y;}update() {if(this.stopped) {return;}this.#y - this.speed;if( this.#y 20) {this.#y 20;}}// ...
}这回再执行 cars[0].#y 20 就会告诉你私有字段不能访问了这样字段只能在类的内部可以访问出了类的范围就不能使用了将来 Java 中会有类似的控制手段。
2. 加强封装
Java 中可以用访问修饰符来对字段或方法进行访问权限控制一共有四种
名称访问权限说明public标识的【字段】及【方法】及【类】谁都能使用protected标识的【字段】及【方法】只有同包类、或是子类内才能使用标识的【字段】及【方法】及【类】只有同包类才能使用默认访问修饰符private标识的【字段】及【方法】只有本类才能使用或内部类
类上能使用的访问修饰符只有 public 和默认两种
private
package com.itheima.encapsulation;
public class Car {private int y; // 私有的private void test() { } // 私有的void update() {// 本类内可以使用System.out.println(this.y);this.test();}}package com.itheima.encapsulation; // 同包测试类
public class Test1 {public static void main(String[] args) {Car car new Car();System.out.println(car.y); // 错误不能访问 private 字段car.test(); // 错误不能访问 private 方法}
}默认
package com.itheima.encapsulation;
public class Car {int y; // 默认的void test() {} // 默认的void update() {// 本类内可以使用System.out.println(this.y);this.test();}}package com.itheima.encapsulation; // 同包测试类
public class Test2 {public static void main(String[] args) {Car car new Car();System.out.println(car.y); // 同包可以使用car.test(); // 同包可以使用}
}package com.itheima; // 不同包测试类
import com.itheima.encapsulation.Car;
public class Test3 {public static void main(String[] args) {Car car new Car();System.out.println(car.y); // 错误不同包不能访问 默认 字段car.test(); // 错误不同包不能访问 默认 方法}
}protected
package com.itheima.encapsulation;
public class Car {protected int y; // 受保护的protected void test() {} // 受保护的// 本类内可以使用void update() {System.out.println(this.y);this.test();}}package com.itheima; // 不同包子类
import com.itheima.encapsulation.Car;
public class SubCar extends Car {void display() {System.out.println(this.y); // 不同包子类内可以使用this.test(); // 不同包子类内可以使用}
}尽可能让访问范围更小
private 默认 protected public尤其是字段建议设置为 private想让子类用 考虑设置为 protected
3. JavaBean
JavaBean 规范
字段私有, 提供公共 get、set、is 方法来访问私有字段 获取字段值用 get 方法获取 boolean 类型字段值用 is 方法修改字段值用 set 方法get、set、is 方法的命名必须是getXXXsetXXXisXXX 其中 XXX 是字段名首字母变大写这些方法可以用 idea 的 ALT Insert 快捷键生成 最好提供一个无参的构造最好实现一个接口 Serializable
例子
class Teacher implements Serializable {private String name; // 小写private boolean married; // 已婚private int age;public boolean isMarried() { // 对 boolean 类型用这种 isXXXreturn this.married;}public void setMarried(boolean married) {this.married married;}// get 方法 用来获取私有字段值public String getName() { // get 后面单词首字母要大写return this.name;}// set 方法 用来修改私有字段值public void setName(String name) {this.name name;}public Teacher(String name, boolean married) {this.name name;this.married married;}public Teacher() {}
}测试类
public class TestJavaBean {public static void main(String[] args) {Teacher t new Teacher(张老师, false);// 全部改用公共方法来间接读写字段值System.out.println(t.getName());System.out.println(t.isMarried());t.setMarried(true);System.out.println(t.isMarried());}
}Java Bean 主要用来封装数据不会提供哪些包含业务逻辑的方法
最后要区分两个名词字段和属性
有 getXXX、setXXX、isXXX 方法的可以称该对象有 XXX 属性首字母变小写 例如上面的 Teacher 类有 name 属性和 married 属性 没有对应 get、set、is 方法的不能说有属性 例如上面的 Teacher 类没有 age 属性
六. 接口
特性1 - 解决单继承
这节课来学习单继承的问题
咱们都知道java 中只支持单继承也就是对于子类来讲只能继承一个父类但这样会出现代码重用方面的问题。看这个例子 如果设计一个父类鸟现在要写一个飞这个方法而且代码实现都一样因此我把这个飞方法放在了父类当中让子类继承然后新写了一个鸭子子类还用写飞这个方法吗不必了继承父类中的飞就可以了。接着来看鸭子还能游泳那么游泳应该放在鸭子中还是鸟中好像应该放在子类鸭子中吧因为如果把游泳放在父类鸟中这样会被所有的子类继承那些原本不会游泳的鸟继承了游泳方法不合理吧但是如果再写一个子类企鹅呢企鹅是鸟吧会游泳吧它和鸭子会都会游泳。但按刚才的讨论游泳方法不能放在父类中因此存在了两份重复的游泳代码。而且不合理又出现了企鹅不会飞照这么说飞放在父类中也不合理也得放到子类中这样一来飞方法也不能重用了就算把飞留在父类中蜻蜓会飞吧但它的父类是昆虫单继承决定了它不能再继承鸟了也就不能重用鸟中的飞方法飞方法又重复了类似的例子还有很多狗熊会游泳吧它的父类是哺乳动物不能和鸭子、企鹅重用游泳方法
上面的问题究其本质是因为 Java 只支持单继承若想补足这方面的短板需要用到接口看这张图 这些继承关系不变但把重复的代码放在接口当中, swimmable 里放游泳方法flyable 里放飞翔方法然后要重用方法的类实现它们。 一个类只能继承一个父类但一个类可以实现多个接口使用接口就解决了刚才的问题 接口里主要提供给的都是方法代表的是具备某方面的能力能游泳能飞翔因此命名上常用 able
它的语法如下
interface A {public default void a() {}
}interface B {public default void b() {}
}// C 从 A, B 两个接口重用方法 a() 和 b()
class C implements A, B {}解决之前的问题
public class TestInterface1 {public static void main(String[] args) {Duck d new Duck();d.swim();d.fly();}
}interface Swimmable {default void swim() {System.out.println(游泳);}
}interface Flyable {default void fly() {System.out.println(飞翔);}
}class Duck implements Swimmable, Flyable {}需要放入接口的方法, 必须加 default 关键字默认方法default 方法只能是 public, public 可以省略
特性2 - 接口多态
刚才我们学习了接口的第一个特性解决单继承的问题接下来看看接口的第二个特性接口方法也支持多态。
方法多态的两个条件需要进一步完善
用父类型代表子类对象或者用接口类型来代表实现类对象必须发生方法重写 #mermaid-svg-oBnQhfDj3i69huU8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oBnQhfDj3i69huU8 .error-icon{fill:#552222;}#mermaid-svg-oBnQhfDj3i69huU8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oBnQhfDj3i69huU8 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oBnQhfDj3i69huU8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oBnQhfDj3i69huU8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oBnQhfDj3i69huU8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oBnQhfDj3i69huU8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oBnQhfDj3i69huU8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oBnQhfDj3i69huU8 .marker.cross{stroke:#333333;}#mermaid-svg-oBnQhfDj3i69huU8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oBnQhfDj3i69huU8 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-oBnQhfDj3i69huU8 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-oBnQhfDj3i69huU8 .nodeLabel,#mermaid-svg-oBnQhfDj3i69huU8 .edgeLabel{color:#131300;}#mermaid-svg-oBnQhfDj3i69huU8 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-oBnQhfDj3i69huU8 .label text{fill:#131300;}#mermaid-svg-oBnQhfDj3i69huU8 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-oBnQhfDj3i69huU8 .classTitle{font-weight:bolder;}#mermaid-svg-oBnQhfDj3i69huU8 .node rect,#mermaid-svg-oBnQhfDj3i69huU8 .node circle,#mermaid-svg-oBnQhfDj3i69huU8 .node ellipse,#mermaid-svg-oBnQhfDj3i69huU8 .node polygon,#mermaid-svg-oBnQhfDj3i69huU8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oBnQhfDj3i69huU8 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-oBnQhfDj3i69huU8 g.clickable{cursor:pointer;}#mermaid-svg-oBnQhfDj3i69huU8 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-oBnQhfDj3i69huU8 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-oBnQhfDj3i69huU8 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-oBnQhfDj3i69huU8 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-oBnQhfDj3i69huU8 .dashed-line{stroke-dasharray:3;}#mermaid-svg-oBnQhfDj3i69huU8 #compositionStart,#mermaid-svg-oBnQhfDj3i69huU8 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #compositionEnd,#mermaid-svg-oBnQhfDj3i69huU8 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #dependencyStart,#mermaid-svg-oBnQhfDj3i69huU8 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #dependencyStart,#mermaid-svg-oBnQhfDj3i69huU8 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #extensionStart,#mermaid-svg-oBnQhfDj3i69huU8 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #extensionEnd,#mermaid-svg-oBnQhfDj3i69huU8 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #aggregationStart,#mermaid-svg-oBnQhfDj3i69huU8 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 #aggregationEnd,#mermaid-svg-oBnQhfDj3i69huU8 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oBnQhfDj3i69huU8 .edgeTerminals{font-size:11px;}#mermaid-svg-oBnQhfDj3i69huU8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» E void e() F void e() G void e() 看这张图上面这是接口E下面这俩类 F、G 实现了接口他俩以后可以叫做实现类看一下这种上下级关系就可以知道它们之间符合向上转型FG能够沿箭头向上转换为接口类型因此能用接口类型代表实现类对象
先来看第一条接口类型可以代表实现类对象
public class TestInterface2 {public static void main(String[] args) {E[] array new E[] {new F(),new G()};}
}
interface E {
}
class F implements E {
}
class G implements E {
}再看第二条方法重写
public class TestInterface2 {public static void main(String[] args) {E[] array new E[] {new F(),new G()};for (int i 0; i array.length; i) {E e array[i];e.e(); // 多态}}
}
interface E {default void e() { System.out.println(e);}
}
class F implements E {Overridepublic void e() { System.out.println(f);}
}
class G implements E {Overridepublic void e() {System.out.println(g);}
}要注意方法重写时要求子类和实现类 方法访问修饰符 父类和接口 方法访问修饰符default 方法的访问修饰符其实是省略了 public实现类中方法的访问修饰符要 public 才不会出错多态性 表面调用的是接口的 E.e() 方法实际会根据 e 的实际类型调用重写方法即 F.e() 和 G.e() 方法
抽象方法
其实要使用接口多态更多地是使用一种抽象方法而非默认方法所谓抽象方法仅有方法声明没有方法体代码。
你看我用抽象方法代替掉这里的默认方法它包含 abstract 关键字而且也只能是 public 的平时这俩关键字都可以省略不写
public class TestInterface2 {public static void main(String[] args) {E[] array new E[] {new F(),new G()};for (int i 0; i array.length; i) {E e array[i];e.e(); // 多态}}
}
interface E {void e(); // 抽象方法没有方法体只能是 public 的省略了 public abstract
}
class F implements E {Overridepublic void e() { // 默认System.out.println(f);}
}
class G implements E {Overridepublic void e() {System.out.println(g);}
}为啥抽象方法设计为不需要方法体呢因为你看
反正多态要求实现类发生方法重写既然方法重写了就调用不到接口方法的代码了既然多态发生时用不到接口中可能的代码还不如让方法体空着
另外抽象方法有个好处它强制了实现类要实施方法重写如果实现类没有重写语法上会报错
特性3 - 接口封装
接口封装的更为彻底
public class TestInterface3 {public static void main(String[] args) {M m new N(); // 用接口类型代表了实现类对象m.m(); // 只能调用接口中定义的方法}
}interface M {void m(); // public abstract
}class N implements M {public String name;Overridepublic void m() {System.out.println(m);}public void n() {System.out.println(n);}
}只能调用到接口中的方法对实现类中的其它方法一无所知接口限制了只能通过方法来使用对象不能直接访问对象的字段
封装的关键在于对外隐藏实现细节接口完美地做到了这一点 经验 在声明方法的参数、返回值定义变量时能用接口类型就用接口类型有更好的扩展性 七. Service
1. 数据与逻辑分离
之前我们讲面向对象设计都是把数据和逻辑放在一起这是理想情况。
现实情况是把对象分为两类一类专门存数据一类专门执行逻辑如下 混在一起有什么缺点呢 数据是方法调用时才能确定的只有请求来了才知道数据pm, yr是什么才能根据它们创建这个既包含数据也包含逻辑的对象而对象的逻辑部分是一样的重复创建感觉有点浪费如果把数据和逻辑分离开 数据对象才需要每次请求来了创建逻辑对象只需一开始创建一次就足够了 因此把数据和逻辑分成 java bean 和 service 能让你的代码更灵活这也是这么做的目的 存数据的就是一个 Java Bean
public class Calculator {private double p;private int m;private double yr;public Calculator(double p, int m, double yr) {this.p p;this.m m;this.yr yr;}// 省略 get set 方法
}存逻辑的叫做 XxxService例如
class CalculatorService0 {public String[] cal(Calculator c) {double p c.getP();int m c.getM();double mr c.getYr() / 12 / 100.0;double pow Math.pow(1 mr, m);double payment p * mr * pow / (pow - 1);return new String[]{NumberFormat.getCurrencyInstance().format(payment * m),NumberFormat.getCurrencyInstance().format(payment * m - p)};}public String[][] details(Calculator c) {double p c.getP();int m c.getM();String[][] a2 new String[m][];double mr c.getYr() / 12 / 100.0;double pow Math.pow(1 mr, m);double payment p * mr * pow / (pow - 1); // 月供for (int i 0; i m; i) {double payInterest c.getP() * mr; // 偿还利息double payPrincipal payment - payInterest; // 偿还本金p - payPrincipal; // 剩余本金a2[i] createRow(payment, i, payInterest, payPrincipal, p);}return a2;}private String[] createRow(double payment, int i, double payInterest, double payPrincipal, double p) {return new String[]{ // 一行的数据(i 1) ,NumberFormat.getCurrencyInstance().format(payment),NumberFormat.getCurrencyInstance().format(payPrincipal),NumberFormat.getCurrencyInstance().format(payInterest),NumberFormat.getCurrencyInstance().format(p)};}}显然Service 根据计算方式不同有多个
要使用多态来避免根据类型的 if else 判断这回可以使用接口接口中添加两个抽象方法 cal 和 details让这几个 Service 都实现它并完成方法重写至于那个重复的 createRow 方法可以作为接口中的 default 方法被实现类重用
public interface Cal {String[] cal(Calculator c);String[][] details(Calculator c);default String[] createRow(double payment, int i, double payInterest, double payPrincipal, double p) {return new String[]{ // 一行的数据(i 1) ,NumberFormat.getCurrencyInstance().format(payment),NumberFormat.getCurrencyInstance().format(payPrincipal),NumberFormat.getCurrencyInstance().format(payInterest),NumberFormat.getCurrencyInstance().format(p)};}
}class CalculatorService0 implements Cal {public String[] cal(Calculator c) {// ...}public String[][] details(Calculator c) {// ...}
}class CalculatorService1 implements Cal {public String[] cal(Calculator c) {// ...}public String[][] details(Calculator c) {// ...}
}class CalculatorService2 implements Cal {public String[] cal(Calculator c) {// ...}public String[][] details(Calculator c) {// ...}
}Controller 的代码变成了
Controller
public class CalController {// ...private Cal[] calArray new Cal[]{new CalculatorService0(),new CalculatorService1(),new CalculatorService2()};RequestMapping(/cal)ResponseBodyString[] cal(double p, int m, double yr, int type) {System.out.println(calArray[type]);return calArray[type].cal(new Calculator(p, m, yr));}RequestMapping(/details)ResponseBodyString[][] details(double p, int m, double yr, int type) {return calArray[type].details(new Calculator(p, m, yr));}}2. 控制反转
一直以来都是我们自己用 new 关键字配合构造方法来创建对象但我们现在用的是 Spring 框架可以把一些创建对象的活交给 Spring 框架去做。
那么 Spring 框架怎么创建对象呢它主要是配合一些注解来完成对象的创建例如我们一直在用的 Controller 注解当 Spring 程序运行时它会检查这些类上有没有加一些特殊注解例如它发现这个类上加了 Controller 注解框架就知道该由框架来创建这个 CalculatorController 对象默认只会创建一个。
这样的注解还有不少我们现在需要掌握的有 Controller 算一个还有一个是 Service试试把这些 Service 类的创建交给 Spring 吧
Service
class CalculatorService0 implements Cal {public String[] cal(Calculator c) {}public String[][] details(Calculator c) {}
}Service
class CalculatorService1 implements Cal {public String[] cal(Calculator c) {}public String[][] details(Calculator c) {}
}Service
class CalculatorService2 implements Cal {public String[] cal(Calculator c) {}public String[][] details(Calculator c) {}
}Service 的作用就是告诉 Spring这个类以后要创建对象的话不归程序员管了啊归 Spring 管其实 Controller 的作用也是类似的控制器对象虽然我们没 new实际由 Spring 来创建了
把对象的创建权交给 Spring 来完成对象的创建权被交出去这称之为控制反转
3. 依赖注入
那么我们的代码里怎么拿到 Spring 创建的对象呢
Controller
public class CalController {// ...Autowiredprivate Cal[] calArray;// ...}这儿又要引入一个相关的名词依赖注入
比如说这里的 控制器 需要 service 才能工作就可以说控制器对象依赖于 service 对象缺了这些依赖对象行吗不行吧。怎么找到这些依赖对象呢如果是框架帮你找到这些依赖对象按一定规则提供给你就称之为依赖注入
怎么让框架帮你找到这些依赖对象呢答案是 Autowired #mermaid-svg-aEXS5qRrePuZbmSt {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-aEXS5qRrePuZbmSt .error-icon{fill:#552222;}#mermaid-svg-aEXS5qRrePuZbmSt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aEXS5qRrePuZbmSt .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-aEXS5qRrePuZbmSt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aEXS5qRrePuZbmSt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aEXS5qRrePuZbmSt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aEXS5qRrePuZbmSt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aEXS5qRrePuZbmSt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aEXS5qRrePuZbmSt .marker.cross{stroke:#333333;}#mermaid-svg-aEXS5qRrePuZbmSt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aEXS5qRrePuZbmSt g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-aEXS5qRrePuZbmSt g.classGroup text .title{font-weight:bolder;}#mermaid-svg-aEXS5qRrePuZbmSt .nodeLabel,#mermaid-svg-aEXS5qRrePuZbmSt .edgeLabel{color:#131300;}#mermaid-svg-aEXS5qRrePuZbmSt .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-aEXS5qRrePuZbmSt .label text{fill:#131300;}#mermaid-svg-aEXS5qRrePuZbmSt .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-aEXS5qRrePuZbmSt .classTitle{font-weight:bolder;}#mermaid-svg-aEXS5qRrePuZbmSt .node rect,#mermaid-svg-aEXS5qRrePuZbmSt .node circle,#mermaid-svg-aEXS5qRrePuZbmSt .node ellipse,#mermaid-svg-aEXS5qRrePuZbmSt .node polygon,#mermaid-svg-aEXS5qRrePuZbmSt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aEXS5qRrePuZbmSt .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-aEXS5qRrePuZbmSt g.clickable{cursor:pointer;}#mermaid-svg-aEXS5qRrePuZbmSt g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-aEXS5qRrePuZbmSt g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-aEXS5qRrePuZbmSt .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-aEXS5qRrePuZbmSt .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-aEXS5qRrePuZbmSt .dashed-line{stroke-dasharray:3;}#mermaid-svg-aEXS5qRrePuZbmSt #compositionStart,#mermaid-svg-aEXS5qRrePuZbmSt .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #compositionEnd,#mermaid-svg-aEXS5qRrePuZbmSt .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #dependencyStart,#mermaid-svg-aEXS5qRrePuZbmSt .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #dependencyStart,#mermaid-svg-aEXS5qRrePuZbmSt .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #extensionStart,#mermaid-svg-aEXS5qRrePuZbmSt .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #extensionEnd,#mermaid-svg-aEXS5qRrePuZbmSt .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #aggregationStart,#mermaid-svg-aEXS5qRrePuZbmSt .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt #aggregationEnd,#mermaid-svg-aEXS5qRrePuZbmSt .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aEXS5qRrePuZbmSt .edgeTerminals{font-size:11px;}#mermaid-svg-aEXS5qRrePuZbmSt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» CalculatorService CalculatorService0 CalculatorService1 CalculatorService2 在 Cal[] 数组上添加 Autowired 即可它是根据类型去问 Spring 要对象Spring 中有很多对象具体要哪个对象呢答案是根据类型要
Cal 表示只要 Spring 创建的对象实现了 Cal 接口就符合条件Cal[] 表示要多个最终的结果是找到 Spring 管理的 CalculatorService0、CalculatorService1、CalculatorService2 三个对象并放入了 Cal[] 数组
4. 由Spring创建JavaBean
Spring 还可以根据请求中的多个查询参数帮我们创建 JavaBean 数据对象
Controller
public class CalController {// ...Autowiredprivate Cal[] calArray;RequestMapping(/cal)ResponseBodyString[] cal(Calculator c, int type) {return calArray[type].cal(c);}RequestMapping(/details)ResponseBodyString[][] details(Calculator c, int type) {return calArray[type].details(c);}}如果提供了无参构造Spring 会优先用它来创建对象并调用 setXXX 方法对属性进行赋值 查询参数有 p那么 Spring 会找一个名为 setP 的方法完成赋值查询参数有 m那么 Spring 会找一个名为 setM 的方法完成赋值查询参数有 yr那么 Spring 会找一个名为 setYr 的方法完成赋值查询参数有 type但 Calculator 对象没有 setType 方法所以 type 并不会存入对象 如果没有无参构造但提供了有参构造Spring 会拿构造方法参数名与查询参数相对应并完成赋值 我们的例子中查询参数有 pmyr构造方法参数名也叫 pmyr根据对应关系完成赋值 注意 不是所有 JavaBean 对象都应该交给 Spring 创建一般只有请求中的数据才会这么做 5. 包结构约定
这节课讲讲包结构约定之前我们也讲过当类的数目比较多时要根据它们的功能进一步划分 package以便更好的管理。 目前可以划分 3 个包
controller 包用来存放控制器类service 包用来存放业务逻辑类dto 包用来存放存数据的 JavaBean 类dto 是 Data Transfer Object数据传输对象的缩写
最后要注意一下入口类的位置必须放在 service, controller 这几个包的上一层为什么呢
这个入口类它还肩负了一个职责查找 Service, Controller 等注解的类然后创建对象。它查找的范围是在这个类的当前 package 之内因此如果 servicecontroller 等包如果不在这个 package 内那么会查找不到
八. 核心类库
1. ArrayList
数组缺点
接下来需要讲解的是 ArrayList它常常被用来替代数组
数组的缺点不能自动扩容比如已经创建了大小为 5 的数组再想放入一个元素就放不下了需要创建更大的数组还得把旧数组的元素迁移过去。
自己来做比较麻烦
public class TestArray {public static void main(String[] args) {String[] arr0 new String[]{a, b, c, d, e};String[] arr1 new String[6];for (int i 0; i arr0.length; i) {arr1[i] arr0[i];}arr1[5] f;System.out.println(Arrays.toString(arr0));System.out.println(Arrays.toString(arr1));}
}想看数组内所有元素循环遍历依次打印是一个办法用 Arrays.toString 方法是更简单的办法
ArrayList 自动扩容
这时可以使用 ArrayList 来替代 String[]它的内部仍然是数组只是封装了更多实用的逻辑
ArrayList list new ArrayList(5); // 指定初始容量为 5, 如果不指定默认是 10
list.add(a);
list.add(b);
list.add(c);
list.add(d);
list.add(e);// 不用担心容量不够, 它内部封装了扩容的逻辑, 每次按原来容量的 1.5 扩容, 向下取整, 如 5-7-10
list.add(f);
System.out.println(list);ArrayList 每次扩容后会预留一些空位避免了频繁扩容封装带来的问题是看不到内部的结构没关系可以使用 debug 工具来调试
Debug 调试
前面说了ArrayList 封装了扩容逻辑这对使用者当然是一件好事就像我们平时使用家用电器似的不需要知道这些电器内部如何工作只需要会按它们对外的几个开关、按钮就足够了。像这个 ArrayList我们只需要会创建对象调用它的add方法就够用了不需要看它内部结构
不过呢有利必然有弊比如你想观察验证它的扩容规则是不是真的是如我所说的 1.5 倍封装就会带来障碍。这里交给大家一招深入对象内部探究它组成结构的好办法debug 调试。
debug 的第一步要添加断点所谓断点就是让代码运行到断点处先停下来不要向下走了这样就能方便我们观察对象状态。在 18 行加一个断点然后记得用调试模式运行代码 要查看 list 的详情先按照下图进行选择查看方式 这样就可以观察 list 的内部结构了 以上介绍了一些基本的调试方法更多的调试方法请关注笑傲篇的高级内容
ArrayList 遍历与泛型
List 的遍历有多种办法这里只介绍最简单的一种
for (Object e : list) {System.out.println(e);
}// 与之对比, 数组也能类似方式进行遍历
for (String s : arr0) {System.out.println(s);
}这种遍历称之为增强 for 循环
上例中的 list 是把元素当作 Object 加入到它的内部再取出来也会当作 Object有时候就会不方便
例如 list 里放的都是整数想做个累加
可以用泛型来限制元素的存取类型例如
ArrayListString 专门存取字符串类型元素ArrayListInteger 专门存取整数类型元素 List 中只能存引用类型不能直接存基本类型
例如
ArrayListInteger list new ArrayListInteger(5);
list.add(1);
list.add(2);
list.add(3);
list.add(4); // 按 Integer 存int sum 0;
for (Integer i : list) { // 按 Integer 取sum i;
}System.out.println(sum);其中等式右边的 String 可以简化为
List 接口 #mermaid-svg-PnIo1zyQW7wiX1YE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PnIo1zyQW7wiX1YE .error-icon{fill:#552222;}#mermaid-svg-PnIo1zyQW7wiX1YE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PnIo1zyQW7wiX1YE .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-PnIo1zyQW7wiX1YE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PnIo1zyQW7wiX1YE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PnIo1zyQW7wiX1YE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PnIo1zyQW7wiX1YE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PnIo1zyQW7wiX1YE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PnIo1zyQW7wiX1YE .marker.cross{stroke:#333333;}#mermaid-svg-PnIo1zyQW7wiX1YE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PnIo1zyQW7wiX1YE g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-PnIo1zyQW7wiX1YE g.classGroup text .title{font-weight:bolder;}#mermaid-svg-PnIo1zyQW7wiX1YE .nodeLabel,#mermaid-svg-PnIo1zyQW7wiX1YE .edgeLabel{color:#131300;}#mermaid-svg-PnIo1zyQW7wiX1YE .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-PnIo1zyQW7wiX1YE .label text{fill:#131300;}#mermaid-svg-PnIo1zyQW7wiX1YE .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-PnIo1zyQW7wiX1YE .classTitle{font-weight:bolder;}#mermaid-svg-PnIo1zyQW7wiX1YE .node rect,#mermaid-svg-PnIo1zyQW7wiX1YE .node circle,#mermaid-svg-PnIo1zyQW7wiX1YE .node ellipse,#mermaid-svg-PnIo1zyQW7wiX1YE .node polygon,#mermaid-svg-PnIo1zyQW7wiX1YE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PnIo1zyQW7wiX1YE .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-PnIo1zyQW7wiX1YE g.clickable{cursor:pointer;}#mermaid-svg-PnIo1zyQW7wiX1YE g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-PnIo1zyQW7wiX1YE g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-PnIo1zyQW7wiX1YE .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-PnIo1zyQW7wiX1YE .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-PnIo1zyQW7wiX1YE .dashed-line{stroke-dasharray:3;}#mermaid-svg-PnIo1zyQW7wiX1YE #compositionStart,#mermaid-svg-PnIo1zyQW7wiX1YE .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #compositionEnd,#mermaid-svg-PnIo1zyQW7wiX1YE .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #dependencyStart,#mermaid-svg-PnIo1zyQW7wiX1YE .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #dependencyStart,#mermaid-svg-PnIo1zyQW7wiX1YE .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #extensionStart,#mermaid-svg-PnIo1zyQW7wiX1YE .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #extensionEnd,#mermaid-svg-PnIo1zyQW7wiX1YE .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #aggregationStart,#mermaid-svg-PnIo1zyQW7wiX1YE .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE #aggregationEnd,#mermaid-svg-PnIo1zyQW7wiX1YE .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-PnIo1zyQW7wiX1YE .edgeTerminals{font-size:11px;}#mermaid-svg-PnIo1zyQW7wiX1YE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} «interface» Collection «interface» List ArrayList List12 ListN ArrayList 是 List 接口的一个实现类除它以外还有 List12 和 ListN 等实现类 他俩的特点是一旦 list 中元素确定就不能再向 list 添加、移除元素
ListInteger list2 List.of(1, 2, 3, 4);
System.out.println(list2.getClass()); // class java.util.ImmutableCollections$ListNListInteger list3 List.of(1, 2);
System.out.println(list3.getClass()); // class java.util.ImmutableCollections$List12of 方法隐藏了内部实现细节对于使用者不需要关心对象的实际类型。 要是真想知道这个对象的类型是什么可以不可以用继承自 Object 的 getClass 方法获得对象的真正类型
2. HashMap
String[] 和 ArrayListString 都有一个缺点查找其中某个元素不高效例如
public static String find1(String value) {String[] array new String[]{小明, 小白, 小黑};for (String s : array) {if (s.equals(value)) {return s;}}return null;
}public static String find2(String value) {ListString list List.of(小明, 小白, 小黑);for (String s : list) {if (s.equals(value)) {return s;}}return null;
}可以想象如果集合大小为 n而要查找的元素是最后一个需要比较 n 次才能找到元素
解决思路人为建立一种【映射】关系比如 #mermaid-svg-1nxJnwnipE8UhQ2d {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .error-icon{fill:#552222;}#mermaid-svg-1nxJnwnipE8UhQ2d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1nxJnwnipE8UhQ2d .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-1nxJnwnipE8UhQ2d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1nxJnwnipE8UhQ2d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1nxJnwnipE8UhQ2d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1nxJnwnipE8UhQ2d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1nxJnwnipE8UhQ2d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1nxJnwnipE8UhQ2d .marker.cross{stroke:#333333;}#mermaid-svg-1nxJnwnipE8UhQ2d svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1nxJnwnipE8UhQ2d .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .cluster-label text{fill:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .cluster-label span{color:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .label text,#mermaid-svg-1nxJnwnipE8UhQ2d span{fill:#333;color:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .node rect,#mermaid-svg-1nxJnwnipE8UhQ2d .node circle,#mermaid-svg-1nxJnwnipE8UhQ2d .node ellipse,#mermaid-svg-1nxJnwnipE8UhQ2d .node polygon,#mermaid-svg-1nxJnwnipE8UhQ2d .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1nxJnwnipE8UhQ2d .node .label{text-align:center;}#mermaid-svg-1nxJnwnipE8UhQ2d .node.clickable{cursor:pointer;}#mermaid-svg-1nxJnwnipE8UhQ2d .arrowheadPath{fill:#333333;}#mermaid-svg-1nxJnwnipE8UhQ2d .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1nxJnwnipE8UhQ2d .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1nxJnwnipE8UhQ2d .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-1nxJnwnipE8UhQ2d .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-1nxJnwnipE8UhQ2d .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1nxJnwnipE8UhQ2d .cluster text{fill:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d .cluster span{color:#333;}#mermaid-svg-1nxJnwnipE8UhQ2d div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1nxJnwnipE8UhQ2d :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0 1 2 小明 小白 小黑 0、1、2是小明、小白、小黑的代号
public static String find1(int key) {String[] array new String[]{小明, 小白, 小黑};if (key 0 || key array.length) {return null;}return array[key];
}public static String find2(int key) {ListString list List.of(小明, 小白, 小黑);if (key 0 || key list.size()) {return null;}return list.get(key);
}可以看到不用逐一比较了当前前提是要知道这个对应关系前面选择 CalculatorService 对象时也用了这个技巧
但 0、1、2 意思不是很直白如果元素数量较多容易弄混如果能起个更有意义的名字是不是更好这就是下面要讲的 Map #mermaid-svg-69c7FPeefQl8Al1l {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-69c7FPeefQl8Al1l .error-icon{fill:#552222;}#mermaid-svg-69c7FPeefQl8Al1l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-69c7FPeefQl8Al1l .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-69c7FPeefQl8Al1l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-69c7FPeefQl8Al1l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-69c7FPeefQl8Al1l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-69c7FPeefQl8Al1l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-69c7FPeefQl8Al1l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-69c7FPeefQl8Al1l .marker.cross{stroke:#333333;}#mermaid-svg-69c7FPeefQl8Al1l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-69c7FPeefQl8Al1l .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-69c7FPeefQl8Al1l .cluster-label text{fill:#333;}#mermaid-svg-69c7FPeefQl8Al1l .cluster-label span{color:#333;}#mermaid-svg-69c7FPeefQl8Al1l .label text,#mermaid-svg-69c7FPeefQl8Al1l span{fill:#333;color:#333;}#mermaid-svg-69c7FPeefQl8Al1l .node rect,#mermaid-svg-69c7FPeefQl8Al1l .node circle,#mermaid-svg-69c7FPeefQl8Al1l .node ellipse,#mermaid-svg-69c7FPeefQl8Al1l .node polygon,#mermaid-svg-69c7FPeefQl8Al1l .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-69c7FPeefQl8Al1l .node .label{text-align:center;}#mermaid-svg-69c7FPeefQl8Al1l .node.clickable{cursor:pointer;}#mermaid-svg-69c7FPeefQl8Al1l .arrowheadPath{fill:#333333;}#mermaid-svg-69c7FPeefQl8Al1l .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-69c7FPeefQl8Al1l .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-69c7FPeefQl8Al1l .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-69c7FPeefQl8Al1l .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-69c7FPeefQl8Al1l .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-69c7FPeefQl8Al1l .cluster text{fill:#333;}#mermaid-svg-69c7FPeefQl8Al1l .cluster span{color:#333;}#mermaid-svg-69c7FPeefQl8Al1l div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-69c7FPeefQl8Al1l :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} bright 小明 white 小白 black 小黑 什么是 Map 呢很简单它就是一组映射关系。
你看刚才的例子中是建立了数组索引和数据元素之间的映射关系根据索引快速找到元素。现在 map 也是一组映射关系只是把索引变得更有意义而已。用 bright 映射小明white 映射小白black 映射小黑前面的 bright、white、black 称之为 key key 需要唯一 后面这些称之为 value
代码如下
public static String find3(String key) {MapString, String map Map.of(bright, 小明,white, 小白,black, 小黑);return map.get(key);
}Map 需要掌握的点
可以用泛型限制 Map 中 key 和 value 的类型Map.of 用来创建不可变的 Map即初始时确定了有哪些 key 和 value之后就不能新增或删除了 根据 key 获取 value用 get(key) 方法 如果想创建可变的 Map用 new HashMap() 存入新的 keyvalue用 put(key, value) 方法 遍历 Map 也有多种方法这里介绍一种相对好用的
for (Map.EntryString, String e : map.entrySet()) {// e.getKey() 获取 key// e.getValue() 获取 value
}其中 Map.Entry 代表一对儿 keyvaluemap.entrySet() 方法来获取所有的 entry
九. 异常处理
1. try - catch
回忆之前我们对异常的使用我们用异常改变了方法执行流程
public class TestTry {public static void main(String[] args) {System.out.println(1);test(0.0);System.out.println(3);}public static void test(double p) {if(p 0.0) {// 异常也是一个对象, 包含的是错误描述throw new IllegalArgumentException(本金必须大于 0); // 1 处}System.out.println(2);}
}输出
1
Exception in thread main java.lang.IllegalArgumentException: 本金必须大于 0at com.itheima.module3.TestTry.test(TestTry.java:13)at com.itheima.module3.TestTry.main(TestTry.java:7)这个例子中执行到 1 处出现了异常后续的输出 2、3 的代码都不会执行了
但如果希望一个方法出现异常后不要影响其它方法继续运行可以用下面的语法来处理
public class TestTry {public static void main(String[] args) {System.out.println(1);try {test(0.0);} catch (IllegalArgumentException e) {System.out.println(e);}System.out.println(3);}public static void test(double p) {if (p 0.0) {throw new IllegalArgumentException(本金必须大于 0);}System.out.println(2);}
}输出
1
java.lang.IllegalArgumentException: 本金必须大于 0
3执行流程为
试着执行 try 块中的代码如果没异常一切照旧现在 try 块内的代码出现了异常test 方法抛出 IllegalArgumentException 异常对象异常抛给 test 方法的上一层main 方法test 的剩余代码不会执行main 方法中的 catch 能够捕捉 IllegalArgumentException 异常对象代码进入 catch 块执行 catch 块内的代码继续运行后续代码 System.out.println(3)
如果把 catch 的异常类型改为 NullPointerException
那么 catch 捉不住 IllegalArgumentException 异常对象这个异常对象会继续向上抛抛给 main 方法的上一层main 方法的上一次是 jvm当 jvm 收到异常就会终止整个程序执行
如果不加 try - catch 块异常对象也会继续从 main 方法抛给 jvmjvm 收到异常终止程序执行
如果把 catch 的异常类型改为 Exception
那么 catch 也能捉住 IllegalArgumentException 异常对象catch 能不能捉异常是看实际异常对象和 catch 所声明的异常类型是否满足是一个的关系即 能够向上转型就能捉不能向上转型就捉不住 异常的继承关系见下一节的图通常会在 catch 处声明 Exception 类型这样就能统一捕获它的所有子类异常对象
2. 继承体系 #mermaid-svg-ZcCxncBYNQirTxa7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ZcCxncBYNQirTxa7 .error-icon{fill:#552222;}#mermaid-svg-ZcCxncBYNQirTxa7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZcCxncBYNQirTxa7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ZcCxncBYNQirTxa7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZcCxncBYNQirTxa7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZcCxncBYNQirTxa7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZcCxncBYNQirTxa7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZcCxncBYNQirTxa7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZcCxncBYNQirTxa7 .marker.cross{stroke:#333333;}#mermaid-svg-ZcCxncBYNQirTxa7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZcCxncBYNQirTxa7 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ZcCxncBYNQirTxa7 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ZcCxncBYNQirTxa7 .nodeLabel,#mermaid-svg-ZcCxncBYNQirTxa7 .edgeLabel{color:#131300;}#mermaid-svg-ZcCxncBYNQirTxa7 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ZcCxncBYNQirTxa7 .label text{fill:#131300;}#mermaid-svg-ZcCxncBYNQirTxa7 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ZcCxncBYNQirTxa7 .classTitle{font-weight:bolder;}#mermaid-svg-ZcCxncBYNQirTxa7 .node rect,#mermaid-svg-ZcCxncBYNQirTxa7 .node circle,#mermaid-svg-ZcCxncBYNQirTxa7 .node ellipse,#mermaid-svg-ZcCxncBYNQirTxa7 .node polygon,#mermaid-svg-ZcCxncBYNQirTxa7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZcCxncBYNQirTxa7 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-ZcCxncBYNQirTxa7 g.clickable{cursor:pointer;}#mermaid-svg-ZcCxncBYNQirTxa7 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZcCxncBYNQirTxa7 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZcCxncBYNQirTxa7 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ZcCxncBYNQirTxa7 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZcCxncBYNQirTxa7 .dashed-line{stroke-dasharray:3;}#mermaid-svg-ZcCxncBYNQirTxa7 #compositionStart,#mermaid-svg-ZcCxncBYNQirTxa7 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #compositionEnd,#mermaid-svg-ZcCxncBYNQirTxa7 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #dependencyStart,#mermaid-svg-ZcCxncBYNQirTxa7 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #dependencyStart,#mermaid-svg-ZcCxncBYNQirTxa7 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #extensionStart,#mermaid-svg-ZcCxncBYNQirTxa7 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #extensionEnd,#mermaid-svg-ZcCxncBYNQirTxa7 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #aggregationStart,#mermaid-svg-ZcCxncBYNQirTxa7 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 #aggregationEnd,#mermaid-svg-ZcCxncBYNQirTxa7 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZcCxncBYNQirTxa7 .edgeTerminals{font-size:11px;}#mermaid-svg-ZcCxncBYNQirTxa7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Throwable String getMessage() void printStackTrace() Exception Error RuntimeException IllegalArgumentException ArrayIndexOutOfBoundsException ArithmeticException NullPointerException Throwable 是异常中最顶层的父类 getMessage() 提供获取异常信息的功能printStackTrace() 会在【标准错误】输出方法的调用链用于定位错误位置 Error 代表无药可救的异常通常这种异常就算 catch 也救不了Exception 代表还可以救一救的异常catch 后可以让程序恢复运行我们见过的异常有 IllegalArgumentException 非法参数异常ArrayIndexOutOfBoundsException 数组越界异常ArithmeticException 算术异常NullPointerException 空指针异常
3. Spring 处理异常
问题为何之前我们控制器中出现的异常不用 try - catch 处理
控制器方法是由 Spring 的方法来调用的因此控制器方法中出现异常会抛给 Spring 方法Spring 的方法内部用了 try - catch 来捕捉异常并在 catch 块中会把异常信息作为响应返回
我们当然也能自己 catch 异常但可悲的是你就算 catch 住异常又能干什么呢还得考虑自己如何把异常信息转换为响应还不如不 catch交给 Spring 去处理
4. 编译异常与运行时异常
异常按语法可以分成两类
运行时异常也称未检查异常 Error 以及它的子类RuntimeException 以及它的子类 编译异常也称检查异常 除掉运行时以外的所有异常都属于编译异常
分别举一个例子throw 一个运行时异常没有额外语法此异常抛给上一层方法来处理
public static void test(double p) {if (p 0.0) {throw new IllegalArgumentException(本金必须大于 0);}System.out.println(2);
}如果 throw 一个编译异常
public static void test(double p) {if (p 0.0) {throw new Exception(本金必须大于 0); // 语法报错了}System.out.println(2);
}编译异常要求在语法上对异常的处理做出选择而且选择是强制的只能下面两个选择二选一 选择1自己处理加 try catch 语句选择2抛给上一层方法做处理用 throws 声明
public static void test(double p) throws Exception {if (p 0.0) {throw new Exception(本金必须大于 0);}System.out.println(2);
}但编译时异常的烦人之处在于当编译时异常抛给上一层方法后上一层方法也被迫做出类似的选择
5. finally
如果无论是否出现异常都一定要执行的代码可以用 finally 语法
try {} catch (Exception e) {} finally {}其中 catch 不是必须的可以 try 与 finally 一起用
那这个 finally 的使用场景是什么呢
以后我们的代码常常需要与一些外部资源打交道外部资源有文件、数据库等等。这些外部资源使用时都有个注意事项就是用完后得把资源及时释放关闭资源都是有限的如果用完不关最终会导致资源耗尽程序也无法继续运行了。将来这边代表资源的对象一般都会提供一个名为 close 的方法用来释放资源。显然在 finally 中调用资源的 close 方法最为科学
public class TestFinally {public static void main(String[] args) {Resource r new Resource();try {System.out.println(使用资源);int i 1 / 0;} catch (Exception e) {System.out.println(e);} finally {r.close();}}
}class Resource implements Closeable {Overridepublic void close() {System.out.println(释放资源);}
}如果资源实现了 Closeable 接口那么可以用 try-with-resource 语法来省略 finally
public class TestFinally {public static void main(String[] args) {// try - with - resourcetry (Resource r new Resource()) {System.out.println(使用资源);int i 1 / 0;} catch (Exception e) {System.out.println(e);}}
}class Resource implements Closeable {Overridepublic void close() {System.out.println(释放资源);}
}eException |-- ArithmeticException RuntimeException |-- NullPointerException * Throwable 是异常中最顶层的父类* getMessage() 提供获取异常信息的功能* printStackTrace() 会在【标准错误】输出方法的调用链用于定位错误位置
* Error 代表无药可救的异常通常这种异常就算 catch 也救不了
* Exception 代表还可以救一救的异常catch 后可以让程序恢复运行
* 我们见过的异常有* IllegalArgumentException 非法参数异常* ArrayIndexOutOfBoundsException 数组越界异常* ArithmeticException 算术异常* NullPointerException 空指针异常## 3. Spring 处理异常问题为何之前我们控制器中出现的异常不用 try - catch 处理* 控制器方法是由 Spring 的方法来调用的因此控制器方法中出现异常会抛给 Spring 方法
* Spring 的方法内部用了 try - catch 来捕捉异常并在 catch 块中会把异常信息作为响应返回我们当然也能自己 catch 异常但可悲的是你就算 catch 住异常又能干什么呢还得考虑自己如何把异常信息转换为响应还不如不 catch交给 Spring 去处理## 4. 编译异常与运行时异常异常按语法可以分成两类* 运行时异常也称未检查异常* Error 以及它的子类* RuntimeException 以及它的子类
* 编译异常也称检查异常* 除掉运行时以外的所有异常都属于编译异常分别举一个例子throw 一个运行时异常没有额外语法此异常抛给上一层方法来处理java
public static void test(double p) {if (p 0.0) {throw new IllegalArgumentException(本金必须大于 0);}System.out.println(2);
}如果 throw 一个编译异常
public static void test(double p) {if (p 0.0) {throw new Exception(本金必须大于 0); // 语法报错了}System.out.println(2);
}编译异常要求在语法上对异常的处理做出选择而且选择是强制的只能下面两个选择二选一 选择1自己处理加 try catch 语句选择2抛给上一层方法做处理用 throws 声明
public static void test(double p) throws Exception {if (p 0.0) {throw new Exception(本金必须大于 0);}System.out.println(2);
}但编译时异常的烦人之处在于当编译时异常抛给上一层方法后上一层方法也被迫做出类似的选择
5. finally
如果无论是否出现异常都一定要执行的代码可以用 finally 语法
try {} catch (Exception e) {} finally {}其中 catch 不是必须的可以 try 与 finally 一起用
那这个 finally 的使用场景是什么呢
以后我们的代码常常需要与一些外部资源打交道外部资源有文件、数据库等等。这些外部资源使用时都有个注意事项就是用完后得把资源及时释放关闭资源都是有限的如果用完不关最终会导致资源耗尽程序也无法继续运行了。将来这边代表资源的对象一般都会提供一个名为 close 的方法用来释放资源。显然在 finally 中调用资源的 close 方法最为科学
public class TestFinally {public static void main(String[] args) {Resource r new Resource();try {System.out.println(使用资源);int i 1 / 0;} catch (Exception e) {System.out.println(e);} finally {r.close();}}
}class Resource implements Closeable {Overridepublic void close() {System.out.println(释放资源);}
}如果资源实现了 Closeable 接口那么可以用 try-with-resource 语法来省略 finally
public class TestFinally {public static void main(String[] args) {// try - with - resourcetry (Resource r new Resource()) {System.out.println(使用资源);int i 1 / 0;} catch (Exception e) {System.out.println(e);}}
}class Resource implements Closeable {Overridepublic void close() {System.out.println(释放资源);}
}