day19天


idea开发工具

  1. 安装
    • 不要安装汉化版本,英语单词多记清楚位置。
  2. IDEA的使用步骤:
    • 在空的工程下新建Module(模块),IDEA中模块类似于eclipse当中的project。
      eclipse的组织方式:workspace—> project

idea的组织方式:project —> module

IDEA工具的一些设置

  1. 包 的显示方式的设置
    📝老杜javase进阶笔记(一) - 图1
  2. 怎么创建module?
    file菜单—>new —> Module
    创建同级别 moudel 的方式
    注意:在你需要保存的位置创建好目录—>然后file菜单—>new —> Module—>在对应按钮选择你要保存的目录
    或者
    在弹出的窗口自己选择路径,手动输入文件夹名称,finsh就行
  • 在New Module窗口上点击左上角的java,然后next
  • 给module起一个名字:chapter15
  • 编写代码,在src目录下新建类,写代码,并运行。
  1. 怎么调节字体大小
    字体设置:file —> settings —> 输入font —> 设置字体样式以及字号大小
  2. 黑色背景设置
    file —> settings —> Appearance&Behavior—> Appearance—> Theme—>Darcula

  3. 如何用idea看到自己以前写的project

    • 📝老杜javase进阶笔记(一) - 图2
  • 这样子选择好自己想要切换的project,点击确定。然后,选择This Windows按钮就可以查看到自己以前写的project啦!
  1. 怎么修改文件名称
    操作如下: 快捷键:shift + F6
    📝老杜javase进阶笔记(一) - 图3
  2. 怎么给程序传递参数:
    第一步:
    📝老杜javase进阶笔记(一) - 图4
    第二步:
    📝老杜javase进阶笔记(一) - 图5
  3. 删除文件的时候将勾去掉
  1. idea中

idea开发的快捷键

  • 快速生成main方法: 快捷键:psvm
  • 快速生成:System.out.println() 快捷键:sout
  • 注意:IDEA是自动保存,不需要ctrl + s
  • 删除一行 快捷键:ctrl + y
  • 怎么运行:
    代码上右键—>run
    或者点击左侧的绿色箭头。
    ctrl + shift + F10

  • 左侧窗口中的列表怎么展开?怎么关闭?
    左箭头关闭。
    右箭头展开。
    上下箭头移动。

  • idea中退出任何窗口,都可以使用esc键盘。(esc就是退出

  • 任何新增/新建/添加的快捷键是:
    alt + insert

  • 窗口变大,变小:
    ctrl + shift + F12
  • 切换java程序:从HelloWorld切换到User
    alt + 右箭头 或者 alt + 左箭头
  • 切换窗口:
    alt + 标号
    alt + 1(打开,关闭)
    alt + 2
  • 提示方法的参数: ctrl + p
  • 注释:
    单行注释:ctrl + /
    多行注释:ctrl + shift + /

  • idea中怎么定位方法/属性/变量?
    光标停到某个单词的下面,这个单词可能是:方法名、变量名、一个类。停到单词下面之后,按ctrl键,出现下划线,鼠标点击跳转。

  • idea当中复制一行是:ctrl + d

  • 如果有红线报错 纠错的方式
    光标移动到红色下划线上面,按住快捷键 alt + 回车

  • 类实现接口
    快捷生成接口中的抽象方法快捷键: alt + 回车 + 选中的抽象方法 + 回车

  • IDEA中生成tostring与equals方法: alt+insert 搜索 tostring 或者 equals

  • shift 敲两次 出现搜索框 搜索类

  • 查看类的方法 ctrl+F12

  • 快捷生成方法的方式: 找到未写然后报红的方法,鼠标点击, alt + 回车

  • shift+F6 选中要改名的文件,快捷修改名称

  • 编辑多行 按住alt 向下拖就行了

  • 快捷键 输入 ifn 回车 (未设置)

这是在Io流那里的快捷键 finally 语句中快速生成 if(??? == null)的


day20 学习方法记住结论,自己写程序去测试加深理解

final 关键字

final关键字

  1. 复习:局部变量没有初始值,需要程序员手动赋值,成员变量有默认值。
  1. final修饰的类无法继承。
  2. final修饰的方法无法覆盖。
  3. final修饰的变量只能赋一次值。
  4. final修饰的引用一旦指向某个对象,则不能再重新指向其它对象,该引用指向对象内部的数据是可以修改的。
    内存图理解:
    📝老杜javase进阶笔记(一) - 图6
  5. final修饰的实例变量必须手动初始化,不能采用系统默认值。(赶在系统赋默认值之前修改也是可以的)

常量

  1. final修饰的实例变量一般和static联合使用,称为常量。常量在方法区中
    public static final double PI = 3.1415926;
    总结:final表示最终的,不可修改的。

抽象类与接口

抽象类

抽象类的理解:

  1. ![](https://gitee.com/hei-yubai/javase/raw/master/img/202203031243073.png#crop=0&crop=0&crop=1&crop=1&id=SMRa5&originHeight=549&originWidth=1359&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  1. 抽象类怎么定义?
    在class前添加abstract关键字就行了。

  2. 抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。

  3. final和abstract不能联合使用,这两个关键字是对立的。
  4. 抽象类的子类可以是抽象类。也可以是非抽象类。
  5. 抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
  6. 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中,且方法不能有方法体。
  7. 抽象方法怎么定义?
    public abstract void doSome(); 不能有大括号{}
    • (重点)一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖/重写/实现。
    • 到目前为止,只是学习了抽象类的基础语法,一个类到底声明为抽象还是非抽象,这个以后慢慢来吧。写代码多的时候,自然就理解了。
  8. 面试题(判断题):java语言中凡是没有方法体的方法都是抽象方法。

    • 不对,错误的
    • Object类中就有很多方法都没有方法体,都是以“;”结尾的,但他们都不是抽象方法,例如:
      public native int hashCode();
      这个方法底层调用了C++写的动态链接库程序。
      前面修饰符列表中没有:abstract。有一个native,表示调用JVM本地程序。

接口

implements

  • 接口的基础语法:
    1. 接口是一种“引用数据类型”
    2. 接口是完全抽象的。
    3. 接口怎么定义:[修饰符列表] interface 接口名{}
    4. 接口支持多继承。
    5. 接口中只有常量+抽象方法。(注意看jdk的版本,若为jdk8版本的话这句话不适用,接口中还有其它方法。)
    6. 接口中所有的元素都是public修饰的
    7. 接口中抽象方法的public abstract可以省略。
    8. 接口中常量的public static final可以省略。
    9. 接口中方法不能有方法体。

day21天 (笔记只体现核心的东西即可,方便以后复习)

  • 接口在开发中的作用
    • 重点:解耦合
  • 面向接口编程,可以降低程序的耦合度,提高程序的扩展力,符合OCP开发原则。

    • 接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。
  • 接口可以解耦合,解开的是谁和谁的耦合!!!

    1. - 任何一个接口都有调用者和实现者。
    2. - **接口可以将调用者和实现者解耦合。**
    3. - **调用者面向接口调用。**
    4. - **实现者面向接口编写实现。**
  • 以后进行大项目的开发,一般都是将项目分离成一个模块一个模块的,模块和模块之间采用接口衔接,降低耦合度。
  1. 注意:接口在开发中的作用,类似于多态在开发中的作用。
    多态:面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力。

    1. /*
    2. public class Master{
    3. public void feed(Dog d){}
    4. public void feed(Cat c){}
    5. //假设又要养其它的宠物,那么这个时候需要再加1个方法。(需要修改代码了)
    6. //这样扩展力太差了,违背了OCP原则(对扩展开放,对修改关闭。)
    7. }
    8. */
    9. public class Master{
    10. public void feed(Animal a){
    11. // 面向Animal父类编程,父类是比子类更抽象的。
    12. //所以我们叫做面向抽象编程,不要面向具体编程。
    13. //这样程序的扩展力就强。
    14. }
    15. }
  2. 接口在开发中的作用?

  • 接口是不是完全的? 是
  • 而我们以后正好要求,面向抽象编程。
  • 面向抽象编程这句话以后可以修改为:面向接口编程。
  • 有了接口就有了可插拔。可插拔表示扩展力很强。不是焊接死的。
  • 主板和内存条之间有插槽,这个插槽就是接口,内存条坏了,可以重新买一个换下来。这叫做高扩展性。(低耦合度。)
    1. 接口在现实世界中是不是到处都是呢?
      螺栓和螺母之间有接口
      灯泡和灯口之间有接口
      笔记本电脑和键盘之间有接口(usb接口,usb接口是不是某个计算机协会制定的协议/规范。)
      接口有什么用?扩展性好。可插拔。
      接口是一个抽象的概念。

类型与类型之间的关系

  1. **is a(继承)、has a(关联)、like a(实现)**implemen

is a(是一个):
Cat is a Animal(猫是一个动物)
凡是能够满足is a的表示“继承关系”
A extends B

has a(有一个):
I has a Pen(我有一支笔)
凡是能够满足has a关系的表示“关联关系”
关联关系通常以“属性”的形式存在。
A{
B b;
}

like a(像一个):
Cooker like a FoodMenu(厨师像一个菜单一样)
凡是能够满足like a关系的表示“实现关系”
实现关系通常是:类实现接口。
A implements B

抽象类和接口有什么区别?

  1. 在这里我们只说一下抽象类和接口在语法上的区别。<br /> 至于以后抽象类和接口应该怎么进行选择,通过后面的项目去体会/学习。
  • 抽象类是半抽象的。
    接口是完全抽象的。
  • 抽象类中有构造方法。
    接口中没有构造方法。
  • 接口和接口之间支持多继承。
    类和类之间只能单继承。
  • 一个类可以同时实现多个接口。
    一个抽象类只能继承一个类(单继承)。
  • 接口中只允许出现常量和抽象方法。(应该jdk的版本,jdk1.8版本不适用)
  • 这里先透露一个信息:
    • 以后接口使用的比抽象类多。一般抽象类使用的还是少。
    • 接口一般都是对“行为”的抽象。

chapter17


package和import

package

  1. 注意事项:
  • 第一:package出现在java源文件第一行。
  • 第二:带有包名怎么编译? javac -d . xxx.java
  • 第三:怎么运行? java 完整类名
    补充:以后说类名的时候,如果带着包名描述,表示完整类名。如果没有带包,描述的话,表示简类名。
    java.util.Scanner 完整类名
    Scanner 简类名
  1. package语法:
  • package 公司域名.项目名称.模块名.功能名称 ;

import

  1. import 什么时候不需要?
    java.lang不需要。
    同包下不需要。
    其它一律都需要。
  2. 怎么用?
    • import 完整类名;
    • import 包名.; 只能到类名那一级,只能指定某个类的类名
      import java.util.Scanner; // java.util.Scanner 完整类名。
      Scanner 是 类名 java.util 是 包名
  3. 同学的疑问:包名.* 这样是不是效率比较低。
    • 这个效率不低,因为编译器在编译的时候,会自动把 变成具体的类名,import java.util.类名;

day 22天


访问控制权限

访问控制权限

5.1 访问控制权限都有哪些? 4个。
private 私有

  1. public 公开
  2. protected 受保护
  3. 默认

5.2 以上的4个访问控制权限:控制的范围是什么? 可以看下面的表格
private 表示私有的,只能在本类中访问

  1. **public 表示公开的,在任何位置都可以访问**
  2. **“默认”表示只能在本类,以及同包下访问。**
  3. **protected表示只能在本类、同包、子类中访问。**

访问控制修饰符

本类 同包 子类 任意位置
public(公开的) 可以访问 可以 可以 可以
protected(受保护的) 可以访问 可以 可以 不可以
默认 可以 可以 不可以 不可以
private(私有的) 可以 不可以 不可以 不可以

范围从大到小排序:public > protected > 默认 > private

5.3 访问控制权限修饰符可以修饰什么?
属性(4个都能用)

  1. 方法(4个都能用)
  2. 类(public和默认能用,其它不行。)
  3. 接口(public和默认能用,其它不行。)

chapter18


object 类

  1. JDK类库的根类

    • 这个老祖宗类中的方法我们需要先研究一下,因为这些方法都是所有子类通用的。任何一个类默认继承Object。就算没有直接继承,最终也会间接继承。
  2. 目前为止我们只需要知道这几个方法即可:

  • protected Object clone() // 负责对象克隆的。
  • int hashCode() // 获取对象哈希值的一个方法。
  • boolean equals(Object obj) // 判断两个对象里的内容是否相等
  • String toString() // 将对象转换成字符串形式
  • protected void finalize() // 垃圾回收器负责调用的方法

API

  1. 什么是API
  • 应用程序编程接口。(Application Program Interface)
  • 整个JDK的类库就是一个 javase 的 API 。
  • 每一个API都会配置一套API帮助文档。
  • SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)

tostring()方法

  1. 以后所有类的toString()方法是需要重写的。
  • 重写规则,越简单越明了就好。
  • System.out.println(引用); 这里会自动调用 “引用” 的toString()方法。
  • String类是SUN写的,toString方法已经重写了。
  1. 源代码长什么样?

    • 源代码如下:

      1. public String toString() {
      2. return this.getClass().getName() + "@" + Integer.toHexString(hashCode());
      3. }
    • 源代码上toString()方法的默认实现是:类名@对象的内存地址转换为十六进制的形式

    • SUN公司设计toString()方法的目的是什么?
      • toString()方法的设计目的是:通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”
      • 其实SUN公司开发java语言的时候,建议所有的子类都去重写toString()方法。
        toString()方法应该是一个简洁的、详实的、易阅读的.
  2. 写一个源代码重写tostring()的方法

equals(object obj)方法

  1. 以后所有类的equals方法也需要重写,因为Object中的equals方法比较的是两个对象的内存地址,我们应该比较内容,所以需要重写。
  2. 默认的equals方法的源代码如下:

    1. public boolean equals(Object obj) {
    2. return (this == obj);
    3. }
  3. SUN公司设计equals方法的目的是什么?

    • 以后编程的过程当中,都要通过equals方法来判断两个对象是否相等。
    • equals方法是判断两个对象是否相等的。
  4. Object类给的这个默认的equals方法够不够用?
    • 在Object类中的equals方法当中,默认采用的是“ == ”判断两个java对象内存地址是否相等。
    • 而“ == ”判断的是两个java对象的内存地址,我们应该判断两个java对象的内容是否相等。所以老祖宗的equals方法不够用,需要子类重写equals。
    • 判断两个java对象是否相等,不能使用“ == ”,因为“ == ”比较的是两个对象的内存地址。
  5. 重写Object类的equals方法:
    怎么重写?
    复制 粘贴 相同的返回值类型、相同的方法名、相同的形式参数列表。
  • 重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。
  • 基本数据类型比较时用: ==
  • 对象和对象比较:调用 equals 方法
  • String类是SUN编写的,所以String类的equals方法重写了。
  • 以后判断两个字符串是否相等,最好不要使用==,要调用字符串对象的equals方法。
    注意:重写equals方法的时候要彻底。
  1. IDEA中生成tostring与equals方法: alt+insert 搜索 tostring 或者 equals
  2. equals(object o)方法重新格式如下:
    ```java public boolean equals(Object obj) {
    1. // 如果obj是空,直接返回false
    2. // 如果obj不是一个MyTime,没必要比较了 ,直接返回false
    3. if(obj == null || !(obj instanceof MyTime)){
    4. return false;
    5. }
    6. // 如果this和obj保存的内存地址相同,没必要比较了,直接返回true。
    7. // 内存地址相同的时候指向的堆内存的对象肯定是同一个。
    8. if(this == obj){
    9. return true;
    10. }
    11. // 程序能够执行到此处说明什么?
    12. // 说明obj不是null,obj是MyTime类型。
    13. MyTime t = (MyTime)obj;
    14. return this.year == t.year && this.month == t.month && this.day == t.day ;
    }

}

  1. - 当方法体中只有一行代码时,大括号可以省略
  2. - IDEA开发不需要写,可以快捷生成。
  3. <a name="R0fnK"></a>
  4. ## String 类
  5. 1. String类已经重写了equals方法,比较两个字符串不能使用==,必须使用equalsequals是通用的。
  6. 2. **String类已经重写了toString方法。**
  7. 3. 总结:
  8. - **java中基本数据类型比较是否相等,使用 ==**
  9. - **java中所有的引用数据类型,统一使用equals方法来判断是否相等。**<br />以上是规矩
  10. 4. 测试 string 类中的 equalstostring方法有没有重写
  11. ```java
  12. public class Test03{
  13. public static void main(String[] args){
  14. // 大部分情况下,采用这样的方式创建字符串对象
  15. String s1 = "hello";
  16. String s2 = "abc";
  17. // 实际上String也是一个类。不属于基本数据类型。
  18. // 既然String是一个类,那么一定存在构造方法。
  19. String s3 = new String("Test1");
  20. String s4 = new String("Test1");
  21. // new两次,两个对象内存地址,s3保存的内存地址和s4保存的内存地址不同。
  22. // == 判断的是内存地址。不是内容。
  23. System.out.println(s3 == s4); // false
  24. // 比较两个字符串能不能使用双等号?
  25. // 不能,必须调用equals方法。
  26. // String类已经重写equals方法了。
  27. System.out.println(s3.equals(s4)); // true
  28. // String类有没有重写toString方法呢?
  29. String x = new String("动力节点");
  30. // 如果String没有重写toString()方法,输出结果:java.lang.String@十六进制的地址
  31. // 经过测试:String类已经重写了toString()方法。
  32. System.out.println(x.toString()); //动力节点
  33. System.out.println(x); //动力节点
  34. }
  35. }

equals 的深层理解

  1. 重写equals方法要彻底的例子:(看懂原理,自己能够写出来)
  1. public class Test05{
  2. public static void main(String[] args){
  3. // 多态(自动类型转换。)
  4. Object o1 = new String("hello world!");
  5. Object o2 = new User();
  6. Object o3 = new Address();
  7. User u1 = new User("zhangsan", new Address("北京","大兴区","11111"));
  8. User u2 = new User("zhangsan", new Address("北京","大兴区","11111"));
  9. System.out.println(u1.equals(u2)); // true
  10. User u3 = new User("zhangsan", new Address("北京","朝阳区","11112"));
  11. System.out.println(u1.equals(u3)); // false
  12. }
  13. }
  14. class User{
  15. // 用户名
  16. String name;
  17. // 用户的住址
  18. Address addr;
  19. public User(){
  20. }
  21. public User(String name, Address addr){
  22. this.name = name;
  23. this.addr = addr;
  24. }
  25. // 重写equals方法
  26. // 重写规则:当一个用户的用户名和家庭住址都相同,表示同一个用户。
  27. // 这个equals判断的是User对象和User对象是否相等。
  28. public boolean equals(Object obj){
  29. // 用户名和用户名相同,住址和住址相同的时候,认定是同一个用户。
  30. if(obj == null || !(obj instanceof User)) return false;
  31. if(this == obj) return true;
  32. User u = (User)obj;
  33. if(this.name.equals(u.name) && this.addr.equals(u.addr)){
  34. return true;
  35. }
  36. return false;
  37. }
  38. }
  39. class Address{
  40. String city;
  41. String street;
  42. String zipcode;
  43. public Address(){
  44. }
  45. public Address(String city,String street,String zipcode){
  46. this.city = city;
  47. this.street = street;
  48. this.zipcode = zipcode;
  49. }
  50. // 注意:这里并没有重写equals方法。
  51. // 这里的equals方法判断的是:Address对象和Address对象是否相等。
  52. public boolean equals(Object obj){
  53. if(obj == null || !(obj instanceof Address)) return false;
  54. if(this == obj) return true;
  55. // 怎么算是家庭住址相同呢?
  56. // 城市相同,街道相同,邮编相同,表示相同。
  57. Address a = (Address)obj;
  58. if(this.city.equals(a.city)
  59. && this.street.equals(a.street)
  60. && this.zipcode.equals(a.zipcode)){
  61. return true;
  62. }
  63. return false;
  64. }
  65. }

finalize()方法 (了解内容)

  1. JDK 9 版本开始方法过时,了解即可
  2. 在Object类中的源代码:

    1. protected void finalize() throws Throwable { }
  3. GC: 负责调用finalize()方法

  4. finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。
  5. 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法。不像equals toString,equals和toString()方法是需要你写代码调用的。
  6. finalize()方法的执行时机:当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。
  7. finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。
    如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中。
  8. 提示:
    java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到,种种条件下,有可能启动,也有可能不启动。
  9. 建议启动的方法:

    1. // 有一段代码可以建议垃圾回收器启动。
    2. if(i % 2 == 0){
    3. System.gc(); // 建议启动垃圾回收器。(只是建议,可能不启动,也可能启动。 启动的概率高了一些。)
    4. }
    5. }
  10. 项目开发中有这样的业务需求:所有对象在JVM中被释放的时候,请记录一下释放时间!!!
    静态代码块 可以记录类加载时机

hashcode(方法)【了解】

  1. 在Object中的hashCode方法是怎样的?

    1. public native int hashCode();
    • 这个方法不是抽象方法,带有native关键字,底层调用C++程序。
    • hashCode()方法返回的是哈希码:实际上就是一个java对象的内存地址,经过哈希算法,得出的一个值。
      所以 hashCode() 方法的执行结果可以等同看做一个java对象的内存地址。
  2. 测试 hashcode() 方法的案例:
    ```java public class Test07{ public static void main(String[] args){

    1. Object o = new Object();
    2. int hashCodeValue = o.hashCode();
    3. // 对象内存地址经过哈希算法转换的一个数字。可以等同看做内存地址。
    4. System.out.println(hashCodeValue); //798154996
    5. MyClass mc = new MyClass();
    6. int hashCodeValue2 = mc.hashCode();
    7. System.out.println(hashCodeValue2); //1392838282
    8. MyClass mc2 = new MyClass();
    9. System.out.println(mc2.hashCode()); // 523429237

    } }

class MyClass { }

  1. <a name="94e0e403"></a>
  2. ## 内部类
  3. 注意:能看懂别人写的程序即可,**尽量不要使用匿名内部类进行开发,使用类实现接口的方式进行开发**
  4. <a name="d0f3b5d7"></a>
  5. ### 1. 内部类
  6. 1. 什么是内部类?<br />**内部类:在类的内部又定义了一个新的类。被称为内部类。**
  7. 2. 内部类的分类:<br />**静态内部类:类似于静态变量**<br />**实例内部类:类似于实例变量**<br />**局部内部类:类似于局部变量**
  8. 3. 使用内部类编写的代码,可读性很差。能不用尽量不用。
  9. 4. **匿名内部类是局部内部类的一种。因为这个类没有名字而得名,叫做匿名内部类。**
  10. 5. 学习匿名内部类主要是让大家以后在阅读别人代码的时候,能够理解。并不代表以后都要这样写。
  11. 6. 匿名内部类有两个缺点:<br />缺点1:太复杂,太乱,可读性差。<br />缺点2:类没有名字,以后想重复使用,不能用。
  12. <a name="51062481"></a>
  13. ### 匿名内部类
  14. 7. 写个案例来验证匿名内部类
  15. - 案例如下:
  16. ```java
  17. class Test01{
  18. // 静态变量
  19. static String country;
  20. // 该类在类的内部,所以称为内部类
  21. // 由于前面有static,所以称为“静态内部类”
  22. static class Inner1{
  23. }
  24. // 实例变量
  25. int age;
  26. // 该类在类的内部,所以称为内部类
  27. // 没有static叫做实例内部类。
  28. class Inner2{
  29. }
  30. // 方法
  31. public void doSome(){
  32. // 局部变量
  33. int i = 100;
  34. // 该类在类的内部,所以称为内部类
  35. // 局部内部类。
  36. class Inner3{
  37. }
  38. }
  39. public void doOther(){
  40. // doSome()方法中的局部内部类Inner3,在doOther()中不能用。
  41. }
  42. // main方法,入口
  43. public static void main(String[] args){
  44. // 调用MyMath中的mySum方法。
  45. MyMath mm = new MyMath();
  46. /*
  47. Compute c = new ComputeImpl();
  48. mm.mySum(c, 100, 200);
  49. */
  50. //合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)
  51. //mm.mySum(new ComputeImpl(), 100, 200);
  52. // 使用匿名内部类,表示这个ComputeImpl这个类没名字了。
  53. // 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。
  54. // 后面的{} 代表了对接口的实现。
  55. // 不建议使用匿名内部类,为什么?
  56. // 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
  57. mm.mySum(new Compute(){
  58. public int sum(int a, int b){
  59. return a + b;
  60. }
  61. }, 200, 300);
  62. }
  63. }
  64. // 负责计算的接口
  65. interface Compute{
  66. // 抽象方法
  67. int sum(int a, int b);
  68. }
  69. // 你自动会在这里编写一个Compute接口的实现类
  70. /*
  71. class ComputeImpl implements Compute{
  72. // 对方法的实现
  73. public int sum(int a, int b){
  74. return a + b;
  75. }
  76. }
  77. */
  78. // 数学类
  79. class MyMath{
  80. // 数学求和方法
  81. public void mySum(Compute c, int x, int y){
  82. int retValue = c.sum(x, y);
  83. System.out.println(x + "+" + y + "=" + retValue);
  84. }
  85. }

chapter 19 数组

day23

养成良好的开发方式,moudle完了创建包,用 alt+insert 快捷键 输入 package 创建包


数组

数组

  1. 什么是数组? (数组实际上是一种简单的数据结构。)
    • Java语言中的数组是一种引用数据类型,不属于基本数据类型,数组的父类是Object
    • 数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合。
    • 数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据。
    • 数组是存储在堆内存当中的
    • 数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象
    • 数组一旦创建,在java中规定,长度不可变
    • 数组的分类:一维数组、二维数组、多维数组…(一维数组较多,二维数组偶尔使用!)
    • 所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数
    • java中的数组要求数组中元素的类型统一,比如:int类型数组只能存储int类型,Person类型数组只能存储Person类型。
    • 数组在内存方面存储的时候,内存地址连续,这是数组存储元素的特点。
    • 所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。(数组中首元素的内存地址作为整个数组对象的内存地址。)
    • 数组中每一个元素都是有下标的,下标从0开始,以1递增。最后一个元素的下标是:length - 1
  2. 数组这种数据结构的优点和缺点是什么?
  • 优点:
  • 第一:每一个元素的内存地址在空间存储上是连续的。
  • 第二:每一个元素类型相同,所以占用空间大小一样。
  • 第三:知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。

eg:数组中存储100个元素,或者存储100万个元素,在元素查询/检索方面,效率是相同的,因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的。(算出一个内存地址,直接定位的。)

  • 缺点:
  • 第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。

  • 第二:数组不能存储大数据量,为什么?
    因为很难在内存空间上找到一块特别大的连续的内存空间。

  • 注意:对于数组中最后一个元素的增删,是没有效率影响的。
  1. 数组在堆内存的内存图:
    📝老杜javase进阶笔记(一) - 图7

一维数组(一)

  1. 怎么声明/定义一个一维数组?
    • 语法格式:
      int[] array1;
      double[] array2;
      boolean[] array3;
      String[] array4;
      Object[] array5;
  2. 怎么初始化一个一维数组呢?
    • 包括两种方式:静态初始化一维数组,动态初始化一维数组。
    • 静态初始化语法格式:
      int[] array = {100, 2100, 300, 55};
    • 通过下标对数组中的元素进行存和取, array[0] 就应该为 100 ;
  • 动态初始化语法格式:
    int[] array = new int[5];
    这里的5表示数组的元素个数,初始化一个5个长度的int类型数组,每个元素默认值0
    String[] names = new String[6];
    初始化6个长度的String类型数组,每个元素默认值null。
  1. eg [ 看懂这个列子 并自己敲出来 ] [ 一维数组的遍历 ] :
  1. public class ArrayTest01 {
  2. public static void main(String[] args) {
  3. // 声明一个int类型的数组,使用静态初始化的方式
  4. int[] a = {1, 100, 10, 20, 55, 689};
  5. // 这是C++风格,不建议java中使用。
  6. //int a[] = {1, 100, 10, 20, 55, 689};
  7. // 所有的数组对象都有length属性
  8. System.out.println("数组中元素的个数" + a.length);
  9. // 数组中每一个元素都有下标
  10. // 通过下标对数组中的元素进行存和取。
  11. // 取(读)
  12. System.out.println("第一个元素 = " + a[0]);
  13. System.out.println("最后一个元素 = " + a[5]);
  14. System.out.println("最后一个元素 = " + a[a.length - 1]);
  15. // 存(改)
  16. // 把第一个元素修改为111
  17. a[0] = 111;
  18. // 把最后一个元素修改为0
  19. a[a.length - 1] = 0;
  20. System.out.println("第一个元素 = " + a[0]);
  21. System.out.println("最后一个元素 = " + a[5]);
  22. // 一维数组怎么遍历呢?
  23. for(int i = 0; i < a.length; i++){
  24. System.out.println(a[i]); // i是从0到5,是下标
  25. }
  26. // 下标为6表示第7个元素,第7个元素没有,下标越界了。会出现什么异常呢?
  27. //System.out.println(a[6]); //ArrayIndexOutOfBoundsException(比较著名的异常。)
  28. // 从最后一个元素遍历到第1个元素
  29. for (int i = a.length - 1; i >= 0; i--) {
  30. System.out.println("颠倒顺序输出-->" + a[i]);
  31. }
  32. }
  33. }
  1. 什么时候采用静态初始化方式,什么时候使用动态初始化方式呢?

    • 当你创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式。
    • 当你创建数组的时候,不确定将来数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间。
    • 案例如下:

      1. public class ArrayTest02 {
      2. public static void main(String[] args) {
      3. // 声明/定义一个数组,采用动态初始化的方式创建
      4. int[] a = new int[4]; // 创建长度为4的int数组,数组中每个元素的默认值是0
      5. // 遍历数组
      6. for (int i = 0; i < a.length; i++) {
      7. System.out.println("数组中下标为" + i + "的元素是:" + a[i]);
      8. }
      9. // 后期赋值
      10. a[0] = 1;
      11. a[1] = 100;
      12. a[2] = 111;
      13. a[3] = 222; // 注意下标别越界。
      14. for (int i = 0; i < a.length; i++) {
      15. System.out.println("数组中下标为" + i + "的元素是:" + a[i]);
      16. }
      17. // 初始化一个Object类型的数组,采用动态初始化方式
      18. Object[] objs = new Object[3]; // 3个长度,动态初始化,所以每个元素默认值是null
      19. for (int i = 0; i < objs.length; i++) {
      20. System.out.println(objs[i]);
      21. }
      22. System.out.println("===============================");
      23. String[] strs = new String[3];
      24. for (int i = 0; i < strs.length; i++) {
      25. System.out.println(strs[i]);
      26. }
      27. // 采用静态初始化的方式
      28. String[] strs2 = {"abc", "def", "xyz"};
      29. for (int i = 0; i < strs2.length; i++) {
      30. System.out.println(strs2[i]);
      31. }
      32. // 存储Object,采用静态初始化呢?
      33. /*Object o1 = new Object();
      34. Object o2 = new Object();
      35. Object o3 = new Object();
      36. Object[] objects = {o1, o2, o3};*/
      37. Object[] objects = {new Object(), new Object(), new Object()};
      38. for (int i = 0; i < objects.length; i++) {
      39. /*Object o = objects[i];
      40. System.out.println(o);*/
      41. System.out.println(objects[i]);
      42. }
      43. }
      44. }
  2. 当一个方法上参数类型为数组时:
    自己总结的步骤:

    • 第一步:声明一个同类型的数组,并完成初始化。
    • 第二步:调用方法的形式参数为数组时,将声明后的同类型数组的名称,作为该方法的形式参数,传入即可
    • 案例1:动态初始化后 再手动赋值
    • 如下:

      1. public class ArrayTest02 {
      2. public static void main(String[] args) {
      3. // 声明/定义一个数组,采用动态初始化的方式创建
      4. int[] a = new int[4]; // 创建长度为4的int数组,数组中每个元素的默认值是0
      5. // 遍历数组
      6. for (int i = 0; i < a.length; i++) {
      7. System.out.println("数组中下标为" + i + "的元素是:" + a[i]);
      8. }
      9. // 后期赋值
      10. a[0] = 1;
      11. a[1] = 100;
      12. a[2] = 111;
      13. a[3] = 222; // 注意下标别越界。
      14. for (int i = 0; i < a.length; i++) {
      15. System.out.println("数组中下标为" + i + "的元素是:" + a[i]);
      16. }
      17. // 初始化一个Object类型的数组,采用动态初始化方式
      18. Object[] objs = new Object[3]; // 3个长度,动态初始化,所以每个元素默认值是null
      19. for (int i = 0; i < objs.length; i++) {
      20. System.out.println(objs[i]);
      21. }
      22. System.out.println("===============================");
      23. String[] strs = new String[3];
      24. for (int i = 0; i < strs.length; i++) {
      25. System.out.println(strs[i]);
      26. }
      27. // 采用静态初始化的方式
      28. String[] strs2 = {"abc", "def", "xyz"};
      29. for (int i = 0; i < strs2.length; i++) {
      30. System.out.println(strs2[i]);
      31. }
      32. // 存储Object,采用静态初始化呢?
      33. /*Object o1 = new Object();
      34. Object o2 = new Object();
      35. Object o3 = new Object();
      36. Object[] objects = {o1, o2, o3};*/
      37. Object[] objects = {new Object(), new Object(), new Object()};
      38. for (int i = 0; i < objects.length; i++) {
      39. /*Object o = objects[i];
      40. System.out.println(o);*/
      41. System.out.println(objects[i]);
      42. }
      43. }
      44. }
  3. 当方法的参数为数组时,用以下语法赋值

    • 案例二如下: ```java public class ArrayTest04 { public static void main(String[] args) { // 静态初始化一维数组 int[] a = {1,2,3}; printArray(a);

      System.out.println(“============================”); // 没有这种语法。 //printArray({1,2,3}); // 如果直接传递一个静态数组的话,语法必须这样写。 printArray(new int[]{1,2,3});

      // 动态初始化一维数组 int[] a2 = new int[4]; printArray(a2);

      System.out.println(“=============================”); printArray(new int[3]); }

    // 为什么要使用静态方法?方便呀,不需要new对象啊。 public static void printArray(int[] array){

    1. for (int i = 0; i < array.length; i++) {
    2. System.out.println(array[i]);
    3. }

    } } ```

String[] args

  1. main方法上面的“String[] args”有什么用?

    • 分析如下:谁负责调用main方法(JVM)
      JVM调用main方法的时候,会自动传一个String数组过来
    • jVM默认传递过来的这个数组对象的长度? 默认0

      • 这个数组什么时候里面会有值呢?
        其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会被转换为“String[] args”

        1. 例如这样运行程序:java ArrayTest05 abc def xyz
      • 那么这个时候JVM会自动将“abc def xyz”通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组当中。

      • 把abc def xyz 转换成字符串数组:{“abc”,”def”,”xyz”}
    • 所以main方法上面的String[] args数组主要是用来接收用户输入参数的。
    • 以下代码表示的含义:数组对象创建了,但是数组中没有任何数据。
      String[] strs = new String[0];
      String[] strs = {}; // 静态初始化数组,里面没东西。
  2. 案例一如下:
  1. public class ArrayTest05 {
  2. // 这个方法程序员负责写出来,JVM负责调用。JVM调用的时候一定会传一个String数组过来。
  3. public static void main(String[] args) {
  4. // JVM默认传递过来的这个数组对象的长度?默认0
  5. // 通过测试得出:args不是null。
  6. System.out.println("JVM给传递过来的String数组参数,它这个数组的长度是?" + args.length);
  7. // 以下这一行代码表示的含义:数组对象创建了,但是数组中没有任何数据。
  8. //String[] strs = new String[0];
  9. //String[] strs = {}; // 静态初始化数组,里面没东西。
  10. //printLength(strs);
  11. // 例如这样运行程序:java ArrayTest05 abc def xyz
  12. // 那么这个时候JVM会自动将“abc def xyz”通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组当中。
  13. // 所以main方法上面的String[] args数组主要是用来接收用户输入参数的。
  14. // 把abc def xyz 转换成字符串数组:{"abc","def","xyz"}
  15. // 遍历数组
  16. for (int i = 0; i < args.length; i++) {
  17. System.out.println(args[i]);
  18. }
  19. }
  20. public static void printLength(String[] args){
  21. System.out.println(args.length); // 0
  22. }
  23. }
  1. string[ ] args有什么作用
    • 模拟一个系统,假设这个系统要使用,必须输入用户名和密码。
  1. public class ArrayTest06 {
  2. // 用户名和密码输入到String[] args数组当中。
  3. public static void main(String[] args) {
  4. if(args.length != 2){
  5. System.out.println("使用该系统时请输入程序参数,参数中包括用户名和密码信息,例如:zhangsan 123");
  6. return;
  7. }
  8. // 程序执行到此处说明用户确实提供了用户名和密码。
  9. // 接下来你应该判断用户名和密码是否正确。
  10. // 取出用户名
  11. String username = args[0];
  12. // 取出密码
  13. String password = args[1];
  14. // 假设用户名是admin,密码是123的时候表示登录成功。其它一律失败。
  15. // 判断两个字符串是否相等,需要使用equals方法。
  16. //if(username.equals("admin") && password.equals("123")){
  17. // 这样编写是不是可以避免空指针异常。
  18. // 采用以下编码风格,及时username和password都是null,也不会出现空指针异常。(这是老程序员给的一条编程经验。)
  19. if("admin".equals(username) && "123".equals(password)){
  20. System.out.println("登录成功,欢迎[" + username + "]回来");
  21. System.out.println("您可以继续使用该系统....");
  22. }else{
  23. System.out.println("验证失败,用户名不存在或者密码错误!");
  24. }
  25. }
  26. }

一维数组(二)

  1. 一维数组的深入:
    • 一维数组的深入,数组中存储的类型为:引用数据类型
    • 对于数组来说,实际上只能存储java对象的“内存地址”。数组中存储的每个元素是“引用”
  2. 数组中存储的是引用的的案例
  1. public class ArrayTest07 {
  2. public static void main(String[] args) {
  3. // a是一个数组
  4. // a[0] 是数组中的一个元素。
  5. // a[1] 是数组中的一个元素。
  6. int[] a = {100, 200, 300};
  7. System.out.println(a[1]);
  8. //a[2] = 400;
  9. int[] array = {1,2,3};
  10. for (int i = 0; i < array.length; i++) {
  11. /*int temp = array[i];
  12. System.out.println(temp);*/
  13. System.out.println(array[i]);
  14. }
  15. // 创建一个Animal类型的数组
  16. Animal a1 = new Animal();
  17. Animal a2 = new Animal();
  18. Animal[] animals = {a1, a2};
  19. // 对Animal数组进行遍历
  20. for (int i = 0; i < animals.length; i++) {
  21. /*Animal a = animals[i];
  22. a.move();*/
  23. // 代码合并
  24. animals[i].move(); // 这个move()方法不是数组的。是数组当中Animal对象的move()方法。
  25. }
  26. // 动态初始化一个长度为2的Animal类型数组。
  27. Animal[] ans = new Animal[2];
  28. // 创建一个Animal对象,放到数组的第一个盒子中。
  29. ans[0] = new Animal();
  30. // Animal数组中只能存放Animal类型,不能存放Product类型。
  31. //ans[1] = new Product();
  32. // Animal数组中可以存放Cat类型的数据,因为Cat是一个Animal。
  33. // Cat是Animal的子类。
  34. ans[1] = new Cat();
  35. // 创建一个Animal类型的数组,数组当中存储Cat和Bird
  36. Cat c = new Cat();
  37. Bird b = new Bird();
  38. Animal[] anis = {c, b};
  39. //Animal[] anis = {new Cat(), new Bird()}; // 该数组中存储了两个对象的内存地址。
  40. for (int i = 0; i < anis.length; i++){
  41. // 这个取出来的可能是Cat,也可能是Bird,不过肯定是一个Animal
  42. // 如果调用的方法是父类中存在的方法不需要向下转型。直接使用父类型引用调用即可。
  43. //anis[i]
  44. //Animal an = anis[i];
  45. //an.move();
  46. //Animal中没有sing()方法。
  47. //anis[i].sing();
  48. // 调用子对象特有方法的话,需要向下转型!!!
  49. if(anis[i] instanceof Cat){
  50. Cat cat = (Cat)anis[i];
  51. cat.catchMouse();
  52. }else if(anis[i] instanceof Bird){
  53. Bird bird = (Bird)anis[i];
  54. bird.sing();
  55. }
  56. }
  57. }
  58. }
  59. class Animal{
  60. public void move(){
  61. System.out.println("Animal move...");
  62. }
  63. }
  64. // 商品类
  65. class Product{
  66. }
  67. // Cat是子类
  68. class Cat extends Animal {
  69. public void move(){
  70. System.out.println("猫在走猫步!");
  71. }
  72. // 特有方法
  73. public void catchMouse(){
  74. System.out.println("猫抓老鼠!");
  75. }
  76. }
  77. // Bird子类
  78. class Bird extends Animal {
  79. public void move(){
  80. System.out.println("Bird Fly!!!");
  81. }
  82. // 特有的方法
  83. public void sing(){
  84. System.out.println("鸟儿在歌唱!!!");
  85. }
  86. }

数组的扩容与拷贝

  1. 在java开发中,数组长度一旦确定不可变,那么数组满了怎么办?
    • 数组满了,需要扩容。
  2. java中对数组的扩容是?
    • 先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中。
  3. 结论:
    • 数组扩容效率较低。因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。
    • 可以在创建数组对象的时候预估计以下多长合适,最好预估准确,这样可以减少数组的扩容次数。提高效率。
  4. 数组的拷贝
  • java中的数组是怎么进行拷贝的呢?
    System.arraycopy(5个参数);

    1. 调用JDK System类中的 arraycopy 方法,来完成数组的拷贝
  • 五个参数的说明:
    (拷贝源,拷贝源的起始位置下标,拷贝目标,拷贝目标的终止位置下表,拷贝起始位置到终止位置中间有几个元素);
    注意事项:

    • 拷贝目标 用动态方式初始化
    • 下标是从0开始的
    • 拷贝目标的终止位置下表:就是从拷贝目标第几个元素开始拷贝
  1. 课堂源码如下:
  1. public class ArrayTest08 {
  2. public static void main(String[] args) {
  3. // java中的数组是怎么进行拷贝的呢?
  4. //System.arraycopy(5个参数);
  5. // 拷贝源(从这个数组中拷贝)
  6. int[] src = {1, 11, 22, 3, 4};
  7. // 拷贝目标(拷贝到这个目标数组上)
  8. int[] dest = new int[20]; // 动态初始化一个长度为20的数组,每一个元素默认值0
  9. // 调用JDK System类中的arraycopy方法,来完成数组的拷贝
  10. //System.arraycopy(src, 1, dest, 3, 2);
  11. // 遍历目标数组
  12. /*
  13. for (int i = 0; i < dest.length; i++) {
  14. System.out.println(dest[i]); // 0 0 0 11 22 ... 0
  15. }
  16. */
  17. System.arraycopy(src, 0, dest, 0, src.length);
  18. for (int i = 0; i < dest.length; i++) {
  19. System.out.println(dest[i]);
  20. }
  21. // 数组中如果存储的元素是引用,可以拷贝吗?当然可以。
  22. String[] strs = {"hello", "world!", "study", "java", "oracle", "mysql", "jdbc"};
  23. String[] newStrs = new String[20];
  24. System.arraycopy(strs, 0, newStrs, 0, strs.length);
  25. for (int i = 0; i < newStrs.length; i++) {
  26. System.out.println(newStrs[i]);
  27. }
  28. System.out.println("================================");
  29. Object[] objs = {new Object(), new Object(), new Object()};
  30. Object[] newObjs = new Object[5];
  31. // 思考一下:这里拷贝的时候是拷贝对象,还是拷贝对象的地址。(地址。)
  32. System.arraycopy(objs, 0, newObjs, 0, objs.length);
  33. for (int i = 0; i < newObjs.length; i++) {
  34. System.out.println(newObjs[i]);
  35. }
  36. }
  37. }

二维数组

  1. 关于java中的二维数组
    • 二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组。
  2. 三维数组是什么?
    三维数组是一个特殊的二维数组,特殊在这个二维数组中每一个元素是一个一维数组。
    实际的开发中使用最多的就是一维数组。二维数组也很少使用。三维数组几乎不用。
  3. 二维数组静态初始化
    int[ ] [ ] array = {{1,1,1},{2,3,4,5},{0,0,0,0},{2,3,4,5},{2,3,4,5},{2,3,4,5},{2,3,4,5}};
  4. 书写格式:
    eg1:
    1. int array[][] ={
    2. {1,1,1},
    3. {2,2,2},
    4. {3,3,3}
    5. };
  1. public class ArrayTest09 {
  2. public static void main(String[] args) {
  3. // 一维数组
  4. int[] array = {100, 200, 300};
  5. System.out.println(array.length); // 3
  6. System.out.println("=======================");
  7. // 二维数组
  8. // 以下代码当中:里面的是4个一维数组。
  9. int[][] a = {
  10. {100, 200, 300},
  11. {30, 20, 40, 50, 60},
  12. {6, 7, 9, 1},
  13. {0}
  14. };
  15. System.out.println(a.length); // 4
  16. System.out.println(a[0].length); // 3
  17. System.out.println(a[1].length); // 5
  18. System.out.println(a[2].length); // 4
  19. System.out.println(a[3].length); // 1
  20. // 里面的是5个一维数组。
  21. int[][] a2 = {
  22. {100, 200, 300},
  23. {30, 20, 40, 50, 60},
  24. {6, 7, 9, 1},
  25. {0},
  26. {1,2,3,4,5}
  27. };
  28. }
  29. }
  1. 关于二维数组中元素的:读 和 改
    a[二维数组中的一维数组的下标】[一维数组中的元素下标]
    a[0] [0]:表示第1个一维数组中的第1个元素。
    a[3 】[ 100 ]:表示第4个一维数组中的第101个元素。
    注意:对于a[3] [100】来说,其中 a[3] 是一个整体。[100]是前面a[3]执行结束的结果然后再下标100.
  2. eg2:
  1. public class ArrayTest10 {
  2. public static void main(String[] args) {
  3. // 二维数组
  4. int[][] a = {
  5. {34,4,65},
  6. {100,200,3900,111},
  7. {0}
  8. };
  9. // 请取出以上二位数中的第1个一维数组。
  10. int[] 我是第1个一维数组 = a[0];
  11. int 我是第1个一维数组中的第1个元素 = 我是第1个一维数组[0];
  12. System.out.println(我是第1个一维数组中的第1个元素);
  13. // 以下代码的由来是因为以上代码的合并导致的。
  14. System.out.println(a[0][0]);
  15. // 取出第2个一维数组当中第3个元素
  16. System.out.println("第二个一维数组中第三个元素:" + a[1][2]);
  17. // 取出第3个一维数组当中第1个元素
  18. System.out.println("第3个一维数组中第1个元素:" + a[2][0]);
  19. // 改
  20. a[2][0] = 11111;
  21. System.out.println(a[2][0]);
  22. // 注意别越界。
  23. //java.lang.ArrayIndexOutOfBoundsException
  24. //System.out.println(a[2][1]);
  25. }
  26. }

java.lang.ArrayIndexOutOfBoundsException
下标越界异常

  1. 二维数组的遍历
    eg3:

    1. public class ArrayTest11 {
    2. public static void main(String[] args) {
    3. // 二维数组
    4. String[][] array = {
    5. {"java", "oracle", "c++", "python", "c#"},
    6. {"张三", "李四", "王五"},
    7. {"lucy", "jack", "rose"}
    8. };
    9. // 遍历二维数组
    10. for(int i = 0; i < array.length; i++){ // 外层循环3次。(负责纵向。)
    11. String[] 一维数组 = array[i];
    12. // 负责遍历一维数组
    13. for(int j = 0; j < 一维数组.length; j++){
    14. System.out.print(一维数组[j] + " ");
    15. }
    16. // 输出换行符
    17. System.out.println();
    18. }
    19. // 合并代码
    20. for(int i = 0; i < array.length; i++){ // 外层循环3次。(负责纵向。)
    21. for(int j = 0; j < array[i].length; j++){
    22. System.out.print(array[i][j] + " ");
    23. }
    24. System.out.println();
    25. }
    26. }
    27. }
  2. 动态初始化二维数组

int[][] array = new int[3][4];
// 3行4列。
// 3个一维数组,每一个一维数组当中4个元素。
```

  1. 当方法的参数为数组时
    eg4 二维数组的遍历:

    1. // 静态初始化
    2. int[][] a = {{1,2,3,4},{4,5,6,76},{1,23,4}};
    3. printArray(a);
    4. // 没有这种语法
    5. //printArray({{1,2,3,4},{4,5,6,76},{1,23,4}});
    6. // 可以这样写。
    7. printArray(new int[][]{{1,2,3,4},{4,5,6,76},{1,23,4}});
    8. }
    9. public static void printArray(int[][] array){
    10. // 遍历二维数组。
    11. for (int i = 0; i < array.length; i++) {
    12. for (int j = 0; j < array[i].length; j++) {
    13. System.out.print(array[i][j] + " ");
    14. }
    15. System.out.println();
    16. }
    17. }
    18. }
    1. public class ArrayTest12 {
    2. public static void main(String[] args) {
    3. // 3行4列。
    4. // 3个一维数组,每一个一维数组当中4个元素。
    5. int[][] array = new int[3][4];
    6. // 二维数组遍历
    7. for (int i = 0; i < array.length; i++) { // 循环3次。
    8. for (int j = 0; j < array[i].length; j++) {
    9. System.out.print(array[i][j] + " ");
    10. }
    11. System.out.println();
    12. }
    13. }

day 23天作业


  1. 编写程序,使用一维数组,模拟栈数据结构。
    要求:
    1、这个栈可以存储java中的任何引用类型的数据。
    2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息。)
    3、在栈中提供pop方法模拟弹栈。(栈空了,也有有提示信息。)
    4、编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作。
    5、假设栈的默认初始化容量是10.(请注意无参数构造方法的编写方式。)
    答案如下:
  1. public class MyStack {
  2. // 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。
  3. // 因为数组是我们学习java的第一个容器。
  4. // 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据
  5. // new Animal()对象可以放进去,new Person()对象也可以放进去。因为Animal和Person的超级父类就是Object。
  6. // 包括String也可以存储进去。因为String父类也是Object。
  7. private Object[] elements;
  8. // 栈帧,永远指向栈顶部元素
  9. // 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。
  10. //private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。
  11. //private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。
  12. private int index;
  13. /**
  14. * 无参数构造方法。默认初始化栈容量10.
  15. */
  16. public MyStack() {
  17. // 一维数组动态初始化
  18. // 默认初始化容量是10.
  19. this.elements = new Object[10];
  20. // 给index初始化
  21. this.index = -1;
  22. }
  23. /**
  24. * 压栈的方法
  25. * @param obj 被压入的元素
  26. */
  27. public void push(Object obj){
  28. if(index >= elements.length - 1){
  29. System.out.println("压栈失败,栈已满!");
  30. return;
  31. }
  32. // 程序能够走到这里,说明栈没满
  33. // 向栈中加1个元素,栈帧向上移动一个位置。
  34. index++;
  35. elements[index] = obj;
  36. // 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。
  37. System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
  38. }
  39. /**
  40. * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
  41. * @return
  42. */
  43. public void pop(){
  44. if(index < 0){
  45. System.out.println("弹栈失败,栈已空!");
  46. return;
  47. }
  48. // 程序能够执行到此处说明栈没有空。
  49. System.out.print("弹栈" + elements[index] + "元素成功,");
  50. // 栈帧向下移动一位。
  51. index--;
  52. System.out.println("栈帧指向" + index);
  53. }
  54. // set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。
  55. // 封装:第一步:属性私有化,第二步:对外提供set和get方法。
  56. public Object[] getElements() {
  57. return elements;
  58. }
  59. public void setElements(Object[] elements) {
  60. this.elements = elements;
  61. }
  62. public int getIndex() {
  63. return index;
  64. }
  65. public void setIndex(int index) {
  66. this.index = index;
  67. }
  68. }
  1. public class MyStackTest {
  2. public static void main(String[] args) {
  3. // 创建一个栈对象,初始化容量是10个。
  4. MyStack stack = new MyStack();
  5. // 调用方法压栈
  6. stack.push(new Object());
  7. stack.push(new Object());
  8. stack.push(new Object());
  9. stack.push(new Object());
  10. stack.push(new Object());
  11. stack.push(new Object());
  12. stack.push(new Object());
  13. stack.push(new Object());
  14. stack.push(new Object());
  15. stack.push(new Object()); // 最后压入的。最先弹出来。(这个才符合栈的数据结构。)
  16. // 压这个元素失败了。
  17. stack.push(new Object());
  18. // 弹栈
  19. stack.pop();
  20. stack.pop();
  21. stack.pop();
  22. stack.pop();
  23. stack.pop();
  24. stack.pop();
  25. stack.pop();
  26. stack.pop();
  27. stack.pop();
  28. stack.pop();
  29. stack.pop();
  30. }
  31. }
  1. 为某个酒店编写程序:酒店管理系统,模拟订房、退房、打印所有房间状态等功能。
    1、该系统的用户是:酒店前台。
    2、酒店使用一个二维数组来模拟。“Room[][] rooms;”
    3、酒店中的每一个房间应该是一个java对象:Room
    4、每一个房间Room应该有:房间编号、房间类型、房间是否空闲.
    5、系统应该对外提供的功能:
    可以预定房间:用户输入房间编号,订房。
    可以退房:用户输入房间编号,退房。
    可以查看所有房间的状态:用户输入某个指令应该可以查看所有房间状态。

答案如下:

  1. public class HotelMgtSystem {
  2. public static void main(String[] args) {
  3. // 创建酒店对象
  4. Hotel hotel = new Hotel();
  5. // 打印房间状态
  6. //hotel.print();
  7. /*
  8. 首先输出一个欢迎页面
  9. */
  10. System.out.println("欢迎使用酒店管理系统,请认真阅读以下使用说明");
  11. System.out.println("功能编号对应的功能:[1]表示查看房间列表。[2]表示订房。[3]表示退房。[0]表示退出系统。");
  12. Scanner s = new Scanner(System.in);
  13. // 一直可以使用(死循环。)。
  14. while(true){
  15. System.out.print("请输入功能编号:");
  16. int i = s.nextInt();
  17. if(i == 1){
  18. // 查看房间列表
  19. hotel.print();
  20. }else if(i == 2){
  21. // 订房
  22. System.out.print("请输入订房编号:");
  23. int roomNo = s.nextInt(); //小姐姐输入房间编号
  24. hotel.order(roomNo);
  25. }else if(i == 3){
  26. // 退房
  27. System.out.print("请输入退房编号:");
  28. int roomNo = s.nextInt(); //小姐姐输入房间编号
  29. hotel.exit(roomNo);
  30. }else if(i == 0){
  31. // 退出系统
  32. System.out.println("再见,欢迎下次再来!");
  33. return;
  34. }else{
  35. // 出错了!
  36. System.out.println("输入功能编号有误,请重新输入!");
  37. }
  38. }
  39. }
  40. }
  1. /*
  2. 酒店对象,酒店中有二维数组,二维数组模拟大厦。
  3. */
  4. public class Hotel {
  5. /**
  6. * 二维数组,模拟大厦所有的房间
  7. */
  8. private Room[][] rooms;
  9. // 盖楼通过构造方法来盖楼。
  10. public Hotel(){
  11. // 一共有几层,每层的房间类型是什么,每个房间的编号是什么。
  12. // 我们可以先写死。一共三层、一层单人间、二层标准间、三层总统套房,每层有10个房间。
  13. /**
  14. * 房间编号
  15. * 1楼:101 102 103 104 105 106..
  16. * 2楼:201 202 203 204 205 206..
  17. * 3楼:301 302 303 304 305 306..
  18. * ...
  19. */
  20. // 动态初始化
  21. rooms = new Room[3][10]; // 3行10列。3层楼,每层10个房间。
  22. // 创建30个Room对象,放到数组当中。
  23. // 怎么放? 二维数组遍历。
  24. for(int i = 0; i < rooms.length; i++){ // i是下标:0 1 2。i+1是楼层:1,2,3
  25. for(int j = 0; j < rooms[i].length; j++){
  26. if(i == 0){
  27. // 一层
  28. rooms[i][j] = new Room((i+1)*100+j+1, "单人间", true);
  29. }else if(i == 1){
  30. // 二层
  31. rooms[i][j] = new Room((i+1)*100+j+1, "标准间", true);
  32. }else if(i == 2){
  33. // 三层
  34. rooms[i][j] = new Room((i+1)*100+j+1, "总统套房", true);
  35. }
  36. }
  37. }
  38. }
  39. // 在酒店对象上提供一个打印房间列表的方法
  40. public void print(){
  41. // 打印所有房间状态,就是遍历二维数组
  42. for(int i = 0; i < rooms.length; i++){
  43. // 里面for循环负责输出一层。
  44. for(int j = 0; j < rooms[i].length; j++) {
  45. Room room = rooms[i][j];
  46. System.out.print(room);
  47. }
  48. // 换行
  49. System.out.println();
  50. }
  51. }
  52. /**
  53. * 订房方法。
  54. * @param roomNo 调用此方法时需要传递一个房间编号过来。这个房间编号是前台小姐姐输入的。
  55. */
  56. public void order(int roomNo){
  57. // 订房最主要的是将房间对象的status修改为false。
  58. // Room对象的status修改为false。
  59. // 假设房间编号207(下标是 rooms[1][6] )
  60. // 通过房间编号演算出下标。获取房间对象。
  61. Room room = rooms[roomNo / 100 - 1][roomNo % 100 - 1];
  62. // 修改为占用。
  63. room.setStatus(false);
  64. System.out.println(roomNo + "已订房!");
  65. }
  66. /**
  67. * 退房
  68. * @param roomNo
  69. */
  70. public void exit(int roomNo){
  71. Room room = rooms[roomNo / 100 - 1][roomNo % 100 - 1];
  72. // 修改为空闲。
  73. room.setStatus(true);
  74. System.out.println(roomNo + "已退房!");
  75. }
  76. }
  1. /**
  2. * 酒店的房间
  3. */
  4. public class Room extends Object{
  5. /**
  6. * 房间编号
  7. * 1楼:101 102 103 104 105 106..
  8. * 2楼:201 202 203 204 205 206..
  9. * 3楼:301 302 303 304 305 306..
  10. * ...
  11. */
  12. private int no;
  13. /**
  14. * 房间类型:标准间 单人间 总统套房
  15. */
  16. private String type;
  17. /**
  18. * 房间状态。
  19. * true表示空闲,房间可以被预定。
  20. * false表示占用,房间不能被预定。
  21. */
  22. private boolean status;
  23. // 构造方法
  24. public Room() {
  25. }
  26. public Room(int no, String type, boolean status) {
  27. this.no = no;
  28. this.type = type;
  29. this.status = status;
  30. }
  31. // setter and getter
  32. public int getNo() {
  33. return no;
  34. }
  35. public void setNo(int no) {
  36. this.no = no;
  37. }
  38. public String getType() {
  39. return type;
  40. }
  41. public void setType(String type) {
  42. this.type = type;
  43. }
  44. // IDEA工具对于boolean类型的变量,生成的get方法的方法名是:isXxx()
  45. // 如果你不喜欢,可以修改为getXxx()
  46. /*public boolean isStatus() {
  47. return status;
  48. }*/
  49. public boolean getStatus() {
  50. return status;
  51. }
  52. public void setStatus(boolean status) {
  53. this.status = status;
  54. }
  55. // equals方法重写
  56. // equals是用来比较两个对象是否相同的。
  57. // 至于怎么比较,这个还是程序员自己定。
  58. // 你认为两个房间的编号相同,就表示同一个房间,那么你写代码比较房间编号就行。
  59. public boolean equals(Object obj) {
  60. if(obj == null || !(obj instanceof Room)) return false;
  61. if(this == obj) return true;
  62. Room room = (Room)obj;
  63. // 当前房间编号 等于 传过来的房间对象的房间编号。认为是同一个房间。
  64. return this.getNo() == room.getNo();
  65. }
  66. // toString方法重写
  67. // toString方法的目的就是将java对象转换成字符串形式。
  68. // 怎么转,转换成什么格式,程序员自己定。目的就是:简单、清晰明了。
  69. // 我不要看对象内存地址。我要看具体的信息。
  70. public String toString() {
  71. //return "[101,单人间,占用]";
  72. //return "[102,单人间,空闲]"; // 写死了。
  73. //动态(把一个变量塞到一个字符串当中,口诀:加一个双引号,双引号中间加两个加号,两个加号中间加变量名。)
  74. return "["+no+","+type+","+(status ? "空闲" : "占用")+"]";
  75. }
  76. // 编写一个临时程序测试以下
  77. // 一会可以删除这个main方法
  78. /*
  79. public static void main(String[] args) {
  80. //Room room = new Room(101, "单人间", true);
  81. Room room = new Room(101, "单人间", false);
  82. //System.out.println(room.toString());
  83. // room是一个引用
  84. // println(引用),会自动调用引用的toString()方法。
  85. System.out.println(room);
  86. Room room1 = new Room(102, "单人间", false);
  87. System.out.println(room.equals(room1));
  88. }
  89. */
  90. // 多行注释:ctrl + shift + /
  91. // 查看一个类的属性和方法:ctrl + F12
  92. }

+++

Day 24

不管equals、tostring()方法用的上用不上,创建了一个类,你都得重写,这是编程素养

写一个方法要写文档注释 这也是基本素养

+++

  1. 处理day23天的作业

    1. 这一个s 是引用 指向了堆内存中的 String类型的对象
  1. // 注意:"abc" 这是一个字符串对象,字符串在java中有优待,不需要new也是一个对象。
  2. // "abc" 字符串也是java对象,属于String类型。
  3. Object[] arr = {new Husband(), new Wife(), "abc"};
  1. 赋值方法:
  2. 可以不在构造方法里面赋值,可以直接赋值,但是还是在调用构造方法的时候执行,完成初始化,并不是在这一行。

数组常见的算法

以下的算法在以后的java实际开发中我们不需要使用的
因为java已经封装好了,直接调用就行
只不过以后面试的时候,可能会有机会碰上

Arrays 工具类

  1. 算法实际上在java中不需要精通,因为java中已经封装好了,要排序就调用方法就行。
  2. 例如:java中提供了一个数组工具类:java.util.Arrays
    Arrays是一个工具类。
    其中有一个sort()方法,可以排序。静态方法,直接使用类名调用就行。
  3. sort()方法的使用

可以对整数型数组排序

  1. /*
  2. 调用了Arrays工具类的sort(int【】 方法)
  3. */
  4. Arrays.sort(nm);
  5. //遍历输出排序后的数组
  6. for (int i = 0 ;i < nm.length;i++){
  7. System.out.println(nm[i]) ;
  1. 工具类:
    • 所有方法都是静态的,直接用类名 调用
    • 主要使用的是两个方法:
      二分法查找 排序

排序分类

排序分为:
内部排序:将需要处理的所有数据加载到内部存储器中进行排序。

  1. 外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。

冒泡排序算法

  1. 基本概念

冒泡排序算法

  • 每一次循环结束之后,都要找出最大的数据,放到参与比较的这堆数据的最右边。(冒出最大的那个气泡。)
  • 核心:
    拿着左边的数字和右边的数字比对,当左边 > 右边的时候,交换位置。
  • 例题如下:
  1. public class BubbleSort {
  2. public static void main(String[] args) {
  3. // 这是int类型的数组对象
  4. //int[] arr = {3, 2, 7, 6, 8};
  5. int[] arr = {9, 8, 10, 7, 6, 0, 11};
  6. // 经过冒泡排序算法对以上数组中元素进行排序
  7. // 冒泡排序算法的核心是什么?
  8. // 7条数据,循环6次。以下的代码可以循环6次。
  9. /*
  10. for(int i = 0; i < arr.length-1; i++){
  11. System.out.println(i);
  12. }
  13. */
  14. // 7条数据,循环6次。以下的代码可以循环6次。(冒泡排序的外层循环采用这种方式)
  15. //int count = 0;
  16. int count2 = 0;
  17. for(int i = arr.length-1; i > 0; i--){
  18. for(int j = 0; j < i; j++){
  19. // 不管是否需要交换位置,总之是要比较一次的。
  20. //count++;
  21. if(arr[j] > arr[j+1]){
  22. // 交换位置。
  23. // arr[j] 和 arr[j+1] 交换
  24. int temp;
  25. temp = arr[j];
  26. arr[j] = arr[j+1];
  27. arr[j+1] = temp;
  28. count2++;
  29. }
  30. }
  31. }
  32. //System.out.println("比较次数:" + count);
  33. System.out.println("交换位置的次数:" + count2); //13
  34. // 输出结果
  35. for (int i = 0; i < arr.length; i++) {
  36. System.out.println(arr[i]);
  37. }
  38. }
  39. }

选择排序算法

  • 每一次从这堆“参与比较的数据当中”找出最小值,拿着这个最小值和“参与比较的这堆最前面的元素”交换位置。
  • 选择排序比冒泡排序好在:每一次的交换位置都是有意义的
  • 选择排序比冒泡排序的效率高。

选择排序的原理:

  • 假设整数数组,里面 min 下标的元素值最小
  • 将 min 下标的元素和其它元素进行比较,找出最小的那个元素放到数组左边,即将最小的元素下标赋值给min,其余的在进行比较。
  • 比较完成后,如果假设的最小值下标和实际比较出来的最小值下标相同,则假设成立。
  • 否则,假设不成立,需要交换两个元素位置,将最小数据放到元素左边。
  • 例题如下:
  1. public class SelectSort {
  2. public static void main(String[] args) {
  3. //int[] arr = {3, 1, 6, 2, 5};
  4. int[] arr = {9, 8, 10, 7, 6, 0, 11};
  5. int count = 0;
  6. int count2 = 0;
  7. // 选择排序
  8. // 5条数据循环4次。(外层循环4次。)
  9. for(int i = 0; i < arr.length - 1; i++){
  10. // i的值是0 1 2 3
  11. // i正好是“参加比较的这堆数据中”最左边那个元素的下标。
  12. //System.out.println(i);
  13. // i是一个参与比较的这堆数据中的起点下标。
  14. // 假设起点i下标位置上的元素是最小的。
  15. int min = i;
  16. for(int j = i+1; j < arr.length; j++){
  17. count++;
  18. //System.out.println("===>" + j);
  19. if(arr[j] < arr[min]){
  20. min = j; //最小值的元素下标是j
  21. }
  22. }
  23. // 当i和min相等时,表示最初猜测是对的。
  24. // 当i和min不相等时,表示最初猜测是错的,有比这个元素更小的元素,
  25. // 需要拿着这个更小的元素和最左边的元素交换位置。
  26. if(min != i){
  27. // 表示存在更小的数据
  28. // arr[min] 最小的数据
  29. // arr[i] 最前面的数据
  30. int temp;
  31. temp = arr[min];
  32. arr[min] = arr[i];
  33. arr[i] = temp;
  34. count2++;
  35. }
  36. }
  37. // 冒泡排序和选择排序实际上比较的次数没变。
  38. // 交换位置的次数减少了。
  39. System.out.println("比较次数" + count); // 21
  40. System.out.println("交换次数:" + count2); // 5
  41. // 排序之后遍历
  42. for (int i = 0; i < arr.length; i++) {
  43. System.out.println(arr[i]);
  44. }
  45. }
  46. }
  47. //1 2 3 4 5
  48. //假设1是最小的,结果1确实是最小的,就不需要交换位置。

数组的元素查找 [ P421 -P422]

单词积累:param 参数

  • 数组元素查找有两种方式:
    第一种方式:一个一个挨着找,直到找到为止。
    第二种方式:二分法查找(算法),这个效率较高。
  • 第一种方式例子:

    1. public class ArraySearch {
    2. public static void main(String[] args) {
    3. // 这个例子演示一下第一种方式
    4. int[] arr = {4,5,5,87,8};
    5. // 需求:找出87的下标。如果没有返回-1
    6. // 一个一个挨着找。
    7. /*
    8. for(int i = 0; i < arr.length;i ++){
    9. if(arr[i] == 87){
    10. System.out.println("87元素的下标是:" + i);
    11. return;
    12. }
    13. }
    14. // 程序执行到此处,表示没有87
    15. System.out.println("87不存在该元素!");
    16. */
    17. // 最好以上的程序封装一个方法,思考:传什么参数?返回什么值?
    18. // 传什么:第一个参数是数组,第二个参数是被查找的元素。
    19. // 返回值:返回被查找的这个元素的下标。如果找不到返回-1.
    20. int index = arraySearch(arr, 5);
    21. System.out.println(index == -1 ? "该元素不存在" : "该元素下标是:" + index);
    22. }
    23. /**
    24. * 从数组中检索某个元素的下标(返回的是第一个元素的下标。)
    25. * @param arr 被检索的数组
    26. * @param ele 被检索的元素
    27. * @return 大于等于0的数表示元素的下标,-1表示该元素不存在
    28. */
    29. public static int arraySearch(int[] arr, int ele) {
    30. for (int i = 0; i < arr.length; i++) {
    31. if(ele == arr[i]){
    32. return i;
    33. }
    34. }
    35. return -1;
    36. }
    37. }

二分法查找

只适合从小到大排序的数组,没有排序的数据是无法查找的。

  1. 原理
  • 先判断数组是否排序
  • 求出中间元素下标,判断中间下标的元素是和要查找的元素的大小,如果相等直接返回元素下标
  • 如果中间元素比要找元素小,修改起始元素下标
  • 如果中间元素比要找元素大,修改结束元素下标
  1. eg:
  • 第二种方法例子
    二分法(折半查找法):
    📝老杜javase进阶笔记(一) - 图8

chapter20 多翻API 熟练掌握没有必要死记硬背

Day25天


常用类

📝老杜javase进阶笔记(一) - 图9

String类 [ P424-]

java.lang.String

  1. 关于Java JDK中内置的一个类:java.lang.String
    1、String表示字符串类型,属于引用数据类型,不属于基本数据类型。
    2、在java中随便使用双引号括起来的都是String对象。例如:”abc”,”def”,”hello world!”,这是3个String对象。
    3、java中规定,双引号括起来的字符串,是不可变的,也就是说”abc”自出生到最终死亡,不可变,不能变成”abcd”,也不能变成”ab”。(不可变是指对象不可变)
    4、在JDK当中双引号括起来的字符串,例如:”abc” “def”都是直接存储在“方法区”的“字符串常量池”当中的。
    5、为什么SUN公司把字符串存储在一个“字符串常量池”当中呢?
    • 因为字符串在实际的开发中使用太频繁,为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
      6、记住:堆区中是运行期分配的,常量池中是编译器分配的
  2. string字符串存储原理 P424
  1. //String s = new String("ABC"); 这一行表示两个对象 在方法1区常量池一个 堆内存一个
  2. String s = "ABC" // 这就是创建对象 s保存的是字符串 ABC 在常量池中的内存地址
  3. String s = "ABC"+"DEF" ; // 常量池中表示三个对象: "ABC" "DEF" "ABCDEF"

第一行代码原理:

  • 是将方法区中的字符串常量存储的 “ABC” 的内存地址,复制一份给堆内存的String类型的对象
    即String类型的对象指向字符串常量区的 “ABC”
  • 然后栈内存的 引用 s , 指向堆内存的String类型的对象(将内存地址复制一份在赋值给s)

第二行代码的原理:

  • 栈内存引用 s 直接 指向方法区字符串常量区的 “ABC”
  • s 引用 保存的是字符串对象在字符串常量池的内存地址

第三行代码的原理:

  • 运行时 对字符串进行拼接 会放在堆内存中
  • 若常量池中没有此对象 先在常量池中创建此对象 然后返回此对象的内存地址

内存图如下:

  1. ![](https://gitee.com/hei-yubai/javase/raw/master/img/202203111601198.png#crop=0&crop=0&crop=1&crop=1&id=PaUVp&originHeight=542&originWidth=1222&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)

注意:

  • 只要采用双引号赋值字符串,那么在编译期将会放到方法区中的字符串的常量池里
  • 如果是运行时对字符串相加或相减会放到堆中
  • 放之前会先验证方法区中是否含有相同的字符串常量,如果存在,把地址返回,如不存在,先将字符串常量放到池中,然后再返回该对象的地址
  1. String s1 = “abc” 和 String s2 = new String(“abc”)
    分析:这是使用new的方式创建的字符串对象。这个代码中的”xy”是从哪里来的?

    1. String s3 = new String("xy");
    • 凡是双引号括起来的都在字符串常量池中有一份。
    • new对象的时候一定在堆内存当中开辟空间。
  2. 字符串之间的比较

    • 通过以下案例的学习,我们知道了,字符串对象之间的比较不能使用“==”
    • “==”不保险。应该调用String类的equals方法,String类已经重写了equals方法,

      1. public class StringTest02 {
      2. public static void main(String[] args) {
      3. String s1 = "hello";
      4. String s2 = "hello";
      5. // 分析结果是true还是false?
      6. // == 双等号比较的是不是变量中保存的内存地址?是的。
      7. System.out.println(s1 == s2); // true

      分析:

    • “hello” 是存储在方法区的字符串常量池当中,所以这个 “hello” 不会新建。(因为这个对象已经存在了!)

      1. String x = new String("xyz");
      2. String y = new String("xyz");
      3. System.out.println(x == y); //false
    • 分析结果是true还是false?
      == 双等号比较的是不是变量中保存的内存地址?是的。

  3. 分析以下程序,一共创建了几个对象

    1. public class StringTest03 {
    2. public static void main(String[] args) {
    3. String s1 = new String("hello");
    4. String s2 = new String("hello");
    5. }
    6. }
    • 一共3个对象:
      方法区字符串常量池中有1个:”hello”
      堆内存当中有两个String对象。

String 类的构造方法

  1. 关于String类的构造方法
  • 第一个:String s = new String(“”);
    要在常量池生成一个对象,堆内存生成一个对象,效率较低

  • 第二个:String s = “ “; 最常用效率较高

  • 第三个:String s = new String(char数组);

  • 第四个:String s = new String(char数组,起始下标,长度);

  • 第五个:String s = new String(byte数组);
  • 第六个:String s = new String(byte数组,起始下标,长度)
  1. 案例:
  1. public class StringTest04 {
  2. public static void main(String[] args) {
  3. // 创建字符串对象最常用的一种方式
  4. String s1 = "hello world!";
  5. // s1这个变量中保存的是一个内存地址。
  6. // 按说以下应该输出一个地址。
  7. // 但是输出一个字符串,说明String类已经重写了toString()方法。
  8. System.out.println(s1);//hello world!
  9. System.out.println(s1.toString()); //hello world!
  10. // 这里只掌握常用的构造方法。
  11. byte[] bytes = {97, 98, 99}; // 97是a,98是b,99是c
  12. String s2 = new String(bytes);
  13. // 前面说过:输出一个引用的时候,会自动调用toString()方法,默认Object的话,会自动输出对象的内存地址。
  14. // 通过输出结果我们得出一个结论:String类已经重写了toString()方法。
  15. // 输出字符串对象的话,输出的不是对象的内存地址,而是字符串本身。
  16. System.out.println(s2.toString()); //abc
  17. System.out.println(s2); //abc
  18. // String(字节数组,数组元素下标的起始位置,长度)
  19. // 将byte数组中的一部分转换成字符串。
  20. String s3 = new String(bytes, 1, 2);
  21. System.out.println(s3); // bc
  22. // 将char数组全部转换成字符串
  23. char[] chars = {'我','是','中','国','人'};
  24. String s4 = new String(chars);
  25. System.out.println(s4);
  26. // 将char数组的一部分转换成字符串
  27. String s5 = new String(chars, 2, 3);
  28. System.out.println(s5);
  29. String s6 = new String("helloworld!");
  30. System.out.println(s6); //helloworld!
  31. }
  32. }

String类的常用方法

  1. String类常用的方法 (多敲 、 多练习 )
    • 只要是对象就能“点.”
  • 需要掌握的方法:

    • charAt(int index [下标] )

      1. // 对象.charAt(int index)
      2. char c = "中国人".charAt(1); // "中国人"是一个字符串String对象。
      3. System.out.println(c); // 国
    • java.lang.ArrayIndexOutOfBoundsException

    • boolean contains(CharSequence s)

判断前面的字符串中是否包含后面的子字符串。

  1. // boolean contains(CharSequence s)
  2. System.out.println("HelloWorld.java".contains(".java")); // true
  3. System.out.println("http://www.baidu.com".contains("https://")); // false
  • boolean endsWith(String suffix)

    判断当前字符串是否以某个子字符串结尾。

    1. System.out.println("test.txt".endsWith(".java")); // false
    2. System.out.println("test.txt".endsWith(".txt")); // true
    3. System.out.println("fdsajklfhdkjlsahfjkdsahjklfdss".endsWith("ss")); // true
  • boolean equals(Object anObject)

    比较两个字符串必须使用equals方法,不能使用“==”

    equals方法有没有调用compareTo方法?
    老版本可以看一下。JDK13中并没有调用compareTo()方法。

compareTo 与equals 方法的区别:

  1. - equals只能看出相等不相等。
  2. - **compareTo方法可以看出是否相等,并且同时还可以看出谁大谁小。**
  1. System.out.println("abc".equals("abc")); // true
  • boolean equalsIgnoreCase(String anotherString)

    1. **判断两个字符串是否相等,并且同时忽略大小写。**
    1. System.out.println("ABc".equalsIgnoreCase("abC")); // true
  • byte[] getBytes()

    • 将字符串对象转换成字节数组

      1. byte[] bytes = "abcdef".getBytes();
      2. for(int i = 0; i < bytes.length; i++){
      3. System.out.println(bytes[i]);
      4. }
  • int indexOf(String str)

    • 判断某个子字符串在当前字符串中第一次出现处的索引(下标)。 ```java System.out.println(“oraclejavac++.netc#phppythonjavaoraclec++”.indexOf(“java”)); // 6
  1. - boolean isEmpty()
  2. - **判断某个字符串是否为“空字符串”。**底层源代码调用的应该是字符串的length()方法。
  3. ```java
  4. //String s = "";
  5. String s = "a";
  6. System.out.println(s.isEmpty());
  • int length()

    • 面试题:判断数组长度和判断字符串长度不一样

      • 判断数组长度是length属性,判断字符串长度是length()方法。 ```java System.out.println(“abc”.length()); // 3

      System.out.println(“”.length()); // 0 ```

  • int lastIndexOf(String str)

    • 判断某个子字符串在当前字符串中最后一次出现的索引(下标)
      1. System.out.println("oraclejavac++javac#phpjavapython".lastIndexOf("java")); //22
  • String replace(CharSequence target, CharSequence replacement)

    • 替换
      1. // String的父接口就是:CharSequence
      2. String newString = "http://www.baidu.com".replace("http://", "https://");
      3. System.out.println(newString); //https://www.baidu.com
      4. // 把以下字符串中的“=”替换成“:”
      5. String newString2 = "name=zhangsan&password=123&age=20".replace("=", ":");
      6. System.out.println(newString2); //name:zhangsan&password:123&age:20
  • String[] split(String regex)

    • 拆分字符串 ```java String[] ymd = “1980-10-11”.split(“-“); //“1980-10-11”以”-“分隔符进行拆分。 for(int i = 0; i < ymd.length; i++){
      1. System.out.println(ymd[i]);
      } String param = “name=zhangsan&password=123&age=20”; String[] params = param.split(“&”); for(int i = 0; i <params.length; i++){
      1. System.out.println(params[i]);
      2. // 可以继续向下拆分,可以通过“=”拆分。
      }

    ○ boolean startsWith(String prefix) // 判断某个字符串是否以某个子字符串开始。 System.out.println(“http://www.baidu.com".startsWith("http“)); // true System.out.println(“http://www.baidu.com".startsWith("https“)); // false ```

  • String substring(int beginIndex) 参数是起始下标。

    • 截取字符串
      1. System.out.println("http://www.baidu.com".substring(7)); //www.baidu.com
  • String substring(int beginIndex, int endIndex)

    • beginIndex起始位置(包括)
    • endIndex结束位置(不包括)
      1. System.out.println("http://www.baidu.com".substring(7, 10)); //www
  • char[] toCharArray()

    • 将字符串转换成char数组
      1. char[] chars = "我是中国人".toCharArray();
      2. for(int i = 0; i < chars.length; i++){
      3. System.out.println(chars[i]);
      4. }
  • String toLowerCase()

    • 转换为小写
      1. System.out.println("ABCDefKXyz".toLowerCase());
  • String toUpperCase();

    1. System.out.println("ABCDefKXyz".toUpperCase());
  • String trim();

    • 去除字符串前后空白
      1. System.out.println(" hello world ".trim());
  • String中只有一个方法是静态的,不需要new对象

    • 这个方法叫做valueOf
    • 作用:将“非字符串”转换成“字符串”

      1. String s1 = String.valueOf(true);
      2. //String s1 = String.valueOf(100);
      3. //String s1 = String.valueOf(3.14);
      4. // 这个静态的valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法吗?
      5. String s1 = String.valueOf(new Customer());
      6. //System.out.println(s1); // 没有重写toString()方法之前是对象内存地址 com.bjpowernode.javase.string.Customer@10f87f48
      7. System.out.println(s1); //我是一个VIP客户!!!!
      8. // 我们是不是可以研究一下println()方法的源代码了?
      9. System.out.println(100);
      10. System.out.println(3.14);
      11. System.out.println(true);
      12. Object obj = new Object();
      13. // 通过源代码可以看出:为什么输出一个引用的时候,会调用toString()方法!!!!
      14. // 本质上System.out.println()这个方法在输出任何数据的时候都是先转换成字符串,再输出。
      15. System.out.println(obj);
      16. System.out.println(new Customer());
  • 了解的方法

    • int compareTo(String anotherString)

      • 字符串之间比较大小不能直接使用 > < ,需要使用compareTo方法。 ```java public class StringTest05 { public static void main(String[] args) {

        int result = “abc”.compareTo(“abc”); System.out.println(result); //0(等于0) 前后一致 10 - 10 = 0

        int result2 = “abcd”.compareTo(“abce”); System.out.println(result2); //-1(小于0) 前小后大 8 - 9 = -1

        int result3 = “abce”.compareTo(“abcd”); System.out.println(result3); // 1(大于0) 前大后小 9 - 8 = 1

        // 拿着字符串第一个字母和后面字符串的第一个字母比较。能分胜负就不再比较了。 System.out.println(“xyz”.compareTo(“yxz”)); // -1

      } }

class Customer { // 重写toString()方法

@Override public String toString() { return “我是一个VIP客户!!!!”; } }

  1. <a name="780adf2a"></a>
  2. ### StringBuffer类 与StringBudilder[P444-P445]
  3. <a name="StringBuffer"></a>
  4. #### StringBuffer
  5. 底层:是一个byte数组,初始化容量是16;将字符串放到了StringBuffer里面了。
  6. 1. 思考:<br />**我们在实际的开发中,如果需要进行字符串的频繁拼接,会有什么问题?**
  7. - **因为java中的字符串是不可变的,每一次拼接都会产生新字符串。**这样会占用大量的方法区内存。造成内存空间的浪费。
  8. - String s = "abc";
  9. s += "hello";<br />就以上两行代码,就导致在方法区字符串常量池当中创建了3个对象:
  10. - "abc"
  11. - "hello"
  12. - "abchello"
  13. ```java
  14. public class StringBufferTest01 {
  15. public static void main(String[] args) {
  16. String s = "";
  17. // 这样做会给java的方法区字符串常量池带来很大的压力。
  18. for(int i = 0; i < 100; i++){
  19. //s += i;
  20. s = s + i;
  21. System.out.println(s);
  22. }
  23. }
  24. }
  1. 如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:

    • java.lang.StringBuffer
    • java.lang.StringBuilder
  2. 如何优化StringBuffer的性能?

    • 在创建StringBuffer的时候尽可能给定一个初始化容量。
    • 最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
    • 关键点:给一个合适的初始化容量。可以提高程序的执行效率。

    • 拼接字符串,以后拼接字符串统一调用 append()方法。实例方法

    • append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。

    • StringBuffer sb = new StringBuffer(100);

括号里面的100是初始容量

  1. public class StringBufferTest02 {
  2. public static void main(String[] args) {
  3. // 创建一个初始化容量为16个byte[] 数组。(字符串缓冲区对象)
  4. StringBuffer stringBuffer = new StringBuffer();
  5. // 拼接字符串,以后拼接字符串统一调用 append()方法。
  6. // append是追加的意思。
  7. stringBuffer.append("a");
  8. stringBuffer.append("b");
  9. stringBuffer.append("d");
  10. stringBuffer.append(3.14);
  11. stringBuffer.append(true);
  12. // append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。
  13. stringBuffer.append(100L);
  14. System.out.println(stringBuffer.toString());
  15. // 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
  16. StringBuffer sb = new StringBuffer(100);
  17. sb.append("hello");
  18. sb.append("world");
  19. sb.append("hello");
  20. sb.append("kitty");
  21. System.out.println(sb);
  22. }
  23. }

java.lang.StringBuilder

  1. tringBuffer和StringBuilder的区别?
    • StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
    • StringBuilder中的方法都没有:synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。
      StringBuffer是线程安全的。
      StringBuilder是非线程安全的。
  1. public class StringBuilderTest01 {
  2. public static void main(String[] args) {
  3. // 使用StringBuilder也是可以完成字符串的拼接。
  4. StringBuilder sb = new StringBuilder();
  5. sb.append(100);
  6. sb.append(true);
  7. sb.append("hello");
  8. sb.append("kitty");
  9. System.out.println(sb);
  10. }
  11. }

String类的面试题

  1. String为什么是不可变的?
    我看过源代码,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变,并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,所以String是不可变的!
    “abc” 无法变成 “abcd”

  2. StringBuilder/StringBuffer为什么是可变的呢?
    我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()…是这样扩容的。所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。

  3. 案例

    1. public class StringBufferTest04 {
    2. public static void main(String[] args) {
    3. // 字符串不可变是什么意思?
    4. // 是说双引号里面的字符串对象一旦创建不可变。
    5. String s = "abc"; //"abc"放到了字符串常量池当中。"abc"不可变。
    6. // s变量是可以指向其它对象的。
    7. // 字符串不可变不是说以上变量s不可变。说的是"abc"这个对象不可变。
    8. s = "xyz";//"xyz"放到了字符串常量池当中。"xyz"不可变。
    9. }
    10. }

image.png

  1. 面试题
    1. public class StringTest1{
    2. public static void main (String[] args){
    3. String a = "java2";
    4. final String b = "java" ;
    5. String d = "java";
    6. String c = b + 2 ;
    7. String e = d + 2 ;
    8. System.out.println(a == c);
    9. System.out.println(a == e);
    10. }
    11. }
    代码理解如下:
    第四行代码:final 修饰的引用 一旦指向某个对象 就不能在指向另一个对象

第五行代码:因为字符串存储在字符串常量池 所以 变量b 和变量d 指向的是字符串常量池中的同一个常量

第六行代码:因为 变量b 是final 修饰的,且为常量 所以变量b 会在编译期 前替换成值, jvm就会识别,即c的值会在编译期确定

记住这个结论:如果运算时,等号右边有一“运算数”不为常量,则得到的结果相当于new创建的一个新的String类型的对象

第七行代码: d+2 这里的d是一个变量 没有被final修饰
编译期:jvm检查语法,不会去识别这个 d 的具体的值,符合语法,编译通过。
运行期:完成 String 字符串的运算 在堆内存开辟地址 然后在将内存地址赋值给e

内存图如下:
image.png

反编译程序如下:
542c4eeffab165548610f422044d3b7.png

八种包装类 [P446-P447]

基本概念

1、java中为8种基本数据类型又对应准备了8种包装类型。8种包装类属于引用数据类型,父类是Object。

2、思考:为什么要再提供8种包装类呢?
因为8种基本数据类型不够用。
所以SUN又提供对应的8种包装类型。

  1. public class IntegerTest01 {
  2. //入口
  3. public static void main(String[] args) {
  4. // 有没有这种需求:调用doSome()方法的时候需要传一个数字进去。
  5. // 但是数字属于基本数据类型,而doSome()方法参数的类型是Object。
  6. // 可见doSome()方法无法接收基本数据类型的数字。那怎么办呢?可以传一个数字对应的包装类进去。
  7. // 把100这个数字经过构造方法包装成对象。
  8. MyInt myInt = new MyInt(100);
  9. // doSome()方法虽然不能直接传100,但是可以传一个100对应的包装类型。
  10. doSome(myInt);
  11. }
  12. public static void doSome(Object obj){
  13. //System.out.println(obj);
  14. System.out.println(obj.toString());
  15. }
  16. }

包装类型

  1. 8种基本数据类型对应的包装类型名是什么?
    | 基本数据类型 | 包装类型 | | —- | —- | | byte | java.lang.Byte(父类Number) | | shot | java.lang.Short(父类Number) | | int | java.lang.Integer(父类Number) | | long | java.lang.Long(父类Number) | | float | java.lang.float(父类Number) | | double | java.lang.Double(父类Number) | | boolean | java.lang.Boolean(父类Object) | | char | java.lang.Character(父类Object) |
  • 以上八种包装类中,重点以java.lang.Integer为代表进行学习,其它的类型照葫芦画瓢就行。
  • 八种包装类中其中6个都是数字对应的包装类,他们的父类都是Number,可以先研究一下Number中公共的方法:
    Number是一个抽象类,无法实例化对象

Number类

Number类中有这样的方法:

  • byte byteValue() 以 byte 形式返回指定的数值。
  • abstract double doubleValue()以 double 形式返回指定的数值。
  • abstract float floatValue()以 float 形式返回指定的数值。
  • abstract int intValue()以 int 形式返回指定的数值。
  • abstract long longValue()以 long 形式返回指定的数值。
  • short shortValue()以 short 形式返回指定的数值。
    这些方法其实所有的数字包装类的子类都有,这些方法是负责拆箱的。
  1. public class IntegerTest02 {
  2. public static void main(String[] args) {
  3. // 123这个基本数据类型,进行构造方法的包装达到了:基本数据类型向引用数据类型的转换。
  4. // 基本数据类型 -(转换为)->引用数据类型(装箱)
  5. Integer i = new Integer(123);
  6. // 将引用数据类型--(转换为)-> 基本数据类型
  7. float f = i.floatValue();
  8. System.out.println(f); //123.0
  9. // 将引用数据类型--(转换为)-> 基本数据类型(拆箱)
  10. int retValue = i.intValue();
  11. System.out.println(retValue); //123
  12. }
  13. }

Integer类的构造方法

  • 关于Integer类的构造方法,有两个:
    • Integer(int)
    • Integer(String) (装箱)
  • 案例如下:
  1. public class IntegerTest03 {
  2. public static void main(String[] args) {
  3. // Java9之后不建议使用这个构造方法了。出现横线表示已过时。
  4. // 将数字100转换成Integer包装类型(int --> Integer)
  5. Integer x = new Integer(100);
  6. System.out.println(x);
  7. // 将String类型的数字,转换成Integer包装类型。(String --> Integer)
  8. Integer y = new Integer("123");
  9. System.out.println(y);
  10. // double -->Double
  11. Double d = new Double(1.23);
  12. System.out.println(d);
  13. // String --> Double
  14. Double e = new Double("3.14");
  15. System.out.println(e);
  16. }
  17. }
  • 通过访问包装类的常量,来获取最大值和最小值(了解)
  • 案例如下:
  1. public class IntegerTest04 {
  2. public static void main(String[] args) {
  3. // 通过访问包装类的常量,来获取最大值和最小值
  4. System.out.println("int的最大值:" + Integer.MAX_VALUE);
  5. System.out.println("int的最小值:" + Integer.MIN_VALUE);
  6. System.out.println("byte的最大值:" + Byte.MAX_VALUE);
  7. System.out.println("byte的最小值:" + Byte.MIN_VALUE);
  8. }
  9. }

自动装箱和自动拆箱(运算的时候,注意它的触发机制)

  • 好消息:在java5之后,引入了一种新特性,自动装箱和自动拆箱
  • 自动装箱:基本数据类型自动转换成包装类。
  • 自动拆箱:包装类自动转换成基本数据类型。
  • 有了自动拆箱之后,Number类中的方法就用不着了!
  • 自动装箱和自动拆箱的好处?

    • 方便编程
  • 基本数据类型 —(自动转换)—> 包装类型:自动装箱

  • 包装类型 —(自动转换)—> 基本数据类型:自动拆箱
  • Integer z = 1000; 等同于:Integer z = new Integer(1000);

    • z是一个引用,z是一个变量,z还是保存了一个对象的内存地址。
  • == 这个运算符不会触发自动拆箱机制。(只有 + - * / 等运算的时候才会。) ```java // z是一个引用,z是一个变量,z还是保存了一个对象的内存地址。

    1. Integer z = 1000; // 等同于:Integer z = new Integer(1000);
    2. // 分析为什么这个没有报错呢?
    3. // +两边要求是基本数据类型的数字,z是包装类,不属于基本数据类型,这里会进行自动拆箱。 将z转换成基本数据类型
    4. // 在java5之前你这样写肯定编译器报错。
    5. System.out.println(z + 1);
  1. ```java
  2. public class IntegerTest05 {
  3. public static void main(String[] args) {
  4. // 900是基本数据类型
  5. // x是包装类型
  6. // 基本数据类型 --(自动转换)--> 包装类型:自动装箱
  7. Integer x = 900;
  8. System.out.println(x);
  9. // x是包装类型
  10. // y是基本数据类型
  11. // 包装类型 --(自动转换)--> 基本数据类型:自动拆箱
  12. int y = x;
  13. System.out.println(y);
  14. Integer a = 1000; // Integer a = new Integer(1000); a是个引用,保存内存地址指向对象。
  15. Integer b = 1000; // Integer b = new Integer(1000); b是个引用,保存内存地址指向对象。
  16. // == 比较的是对象的内存地址,a和b两个引用中保存的对象内存地址不同。
  17. // == 这个运算符不会触发自动拆箱机制。(只有+ - * /等运算的时候才会。)
  18. System.out.println(a == b); //false
  19. }
  20. }

Integer面试重要题目

  • java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据不需要再new了,直接从整数型常量池当中取出来。
  • 原理:x变量中保存的对象的内存地址和y变量中保存的对象的内存地址是一样的。
  1. 这个题目是Integer非常重要的面试题。
  2. public class IntegerTest06 {
  3. public static void main(String[] args) {
  4. Integer a = 128;
  5. Integer b = 128;
  6. System.out.println(a == b); //false
  7. Integer x = 127;
  8. Integer y = 127;
  9. // == 永远判断的都是两个对象的内存地址是否相同。
  10. System.out.println(x == y); //true
  11. }
  12. }

程序内存图:
池就是缓存机制image.png

Integer类当中常用的方法

  • 网页上文本框中输入的100实际上是”100”字符串。后台数据库中要求存储100数字,此时java程序需要将”100”转换成100数字。


  • 编译的时候没问题,一切符合java语法,运行时会不会出问题呢?

    • 不是一个“数字”可以包装成Integer吗?
      • 不能。运行时出现异常 java.lang.NumberFormatException
        1. Integer a = new Integer("123");
        2. //Integer a = new Integer("中文"); 这是不行的
  • static int parseInt(String s)(重点方法) ```java public class IntegerTest07 { public static void main(String[] args) {

    1. // 手动装箱
    2. Integer x = new Integer(1000);
    3. // 手动拆箱。
    4. int y = x.intValue();
    5. System.out.println(y);
  1. // 重点方法
  2. // static int parseInt(String s)
  3. // 静态方法,传参String,返回int
  4. int retValue = Integer.parseInt("123"); // String -转换-> int
  5. //int retValue = Integer.parseInt("中文"); // NumberFormatException
  6. System.out.println(retValue + 100);
  7. // 照葫芦画瓢
  8. double retValue2 = Double.parseDouble("3.14");
  9. System.out.println(retValue2 + 1); //4.140000000000001(精度问题)
  10. float retValue3 = Float.parseFloat("1.0");
  11. System.out.println(retValue3 + 1); //2.0
  12. // -----------------------------------以下内容作为了解,不需要掌握---------------------------------------
  13. // static String toBinaryString(int i)
  14. // 静态的:将十进制转换成二进制字符串。
  15. String binaryString = Integer.toBinaryString(3);
  16. System.out.println(binaryString); //"11" 二进制字符串
  17. // static String toHexString(int i)
  18. // 静态的:将十进制转换成十六进制字符串。
  19. String hexString = Integer.toHexString(16);
  20. System.out.println(hexString); // "10"
  21. // 十六进制:1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19 1a
  22. hexString = Integer.toHexString(17);
  23. System.out.println(hexString); // "11"
  24. //static String toOctalString(int i)
  25. // 静态的:将十进制转换成八进制字符串。
  26. String octalString = Integer.toOctalString(8);
  27. System.out.println(octalString); // "10"
  28. System.out.println(new Object()); //java.lang.Object@6e8cf4c6
  29. // valueOf方法作为了解
  30. //static Integer valueOf(int i)
  31. // 静态的:int-->Integer
  32. Integer i1 = Integer.valueOf(100);
  33. System.out.println(i1);
  34. // static Integer valueOf(String s)
  35. // 静态的:String-->Integer
  36. Integer i2 = Integer.valueOf("100");
  37. System.out.println(i2);
  38. }

}

  1. <a name="iFHlJ"></a>
  2. #### String int Integer之间互相转换
  3. ```java
  4. public class IntegerTest08 {
  5. public static void main(String[] args) {
  6. // String --> int
  7. int i1 = Integer.parseInt("100"); // i1是100数字
  8. System.out.println(i1 + 1); // 101
  9. // int --> String
  10. String s2 = i1 + ""; // "100"字符串
  11. System.out.println(s2 + 1); // "1001"
  12. // int --> Integer
  13. // 自动装箱
  14. Integer x = 1000;
  15. // Integer --> int
  16. // 自动拆箱
  17. int y = x;
  18. // String --> Integer
  19. Integer k = Integer.valueOf("123");
  20. // Integer --> String
  21. String e = String.valueOf(k);
  22. }
  23. }

image.png

Date类(Java中对日期的处理)

java中对日期的处理


这个案例最主要掌握:
知识点1:怎么获取系统当前时间
知识点2:String —-> Date
知识点3:Date —-> String

  1. public class DateTest01 {
  2. public static void main(String[] args) throws Exception {
  3. // 获取系统当前时间(精确到毫秒的系统当前时间)
  4. // 直接调用无参数构造方法就行。
  5. Date nowTime = new Date();
  6. // java.util.Date类的toString()方法已经被重写了。
  7. // 输出的应该不是一个对象的内存地址,应该是一个日期字符串。
  8. //System.out.println(nowTime); //Thu Mar 05 10:51:06 CST 2020
  9. // 日期可以格式化吗?
  10. // 将日期类型Date,按照指定的格式进行转换:Date --转换成具有一定格式的日期字符串-->String
  11. // SimpleDateFormat是java.text包下的。专门负责日期格式化的。
  12. /*
  13. yyyy 年(年是4位)
  14. MM 月(月是2位)
  15. dd 日
  16. HH 时
  17. mm 分
  18. ss 秒
  19. SSS 毫秒(毫秒3位,最高999。1000毫秒代表1秒)
  20. 注意:在日期格式中,除了y M d H m s S这些字符不能随便写之外,剩下的符号格式自己随意组织。
  21. */
  22. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
  23. //SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
  24. //SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
  25. String nowTimeStr = sdf.format(nowTime);
  26. System.out.println(nowTimeStr);
  27. // 假设现在有一个日期字符串String,怎么转换成Date类型?
  28. // String --> Date
  29. String time = "2008-08-08 08:08:08 888";
  30. //SimpleDateFormat sdf2 = new SimpleDateFormat("格式不能随便写,要和日期字符串格式相同");
  31. // 注意:字符串的日期格式和SimpleDateFormat对象指定的日期格式要一致。不然会出现异常:java.text.ParseException
  32. SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
  33. Date dateTime = sdf2.parse(time);
  34. System.out.println(dateTime); //Fri Aug 08 08:08:08 CST 2008
  35. }
  36. }

获取毫秒数

  1. 获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数。

1秒 = 1000毫秒

  1. public class DateTest02 {
  2. public static void main(String[] args) {
  3. // 获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数。
  4. long nowTimeMillis = System.currentTimeMillis();
  5. System.out.println(nowTimeMillis); //1583377912981
  6. // 统计一个方法耗时
  7. // 在调用目标方法之前记录一个毫秒数
  8. long begin = System.currentTimeMillis();
  9. print();
  10. // 在执行完目标方法之后记录一个毫秒数
  11. long end = System.currentTimeMillis();
  12. System.out.println("耗费时长"+(end - begin)+"毫秒");
  13. }
  14. // 需求:统计一个方法执行所耗费的时长
  15. public static void print(){
  16. for(int i = 0; i < 1000000000; i++){
  17. System.out.println("i = " + i);
  18. }
  19. }
  20. }
  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. public class DateTest03 {
  4. public static void main(String[] args) {
  5. // 这个时间是什么时间?
  6. // 1970-01-01 00:00:00 001
  7. Date time = new Date(1); // 注意:参数是一个毫秒
  8. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
  9. String strTime = sdf.format(time);
  10. // 北京是东8区。差8个小时。
  11. System.out.println(strTime); // 1970-01-01 08:00:00 001
  12. // 获取昨天的此时的时间。
  13. Date time2 = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);
  14. String strTime2 = sdf.format(time2);
  15. System.out.println(strTime2); //2020-03-04 11:44:14 829
  16. // 获取“去年的今天”的时间
  17. // 自己玩。
  18. }
  19. }

System类的总结

简单总结一下System类的相关属性和方法:
System.out 【out是System类的静态变量。】
System.out.println() 【println()方法不是System类的,是PrintStream类的方法。】
System.gc() 建议启动垃圾回收器
System.currentTimeMillis() 获取自1970年1月1日到系统当前时间的总毫秒数。
System.exit(0) 退出JVM。
System.arrayCopy(五个参数);

Random类(产生随机数)

  1. 案例如下;

    1. public class RandomTest01 {
    2. public static void main(String[] args) {
    3. // 创建随机数对象
    4. Random random = new Random();
    5. // 随机产生一个int类型取值范围内的数字。
    6. int num1 = random.nextInt();
    7. System.out.println(num1);
    8. // 产生[0~100]之间的随机数。不能产生101。
    9. // nextInt翻译为:下一个int类型的数据是101,表示只能取到100.
    10. int num2 = random.nextInt(101); //不包括101
    11. System.out.println(num2);
    12. }
    13. }
  2. 课堂练习 ```java import java.util.Arrays; import java.util.Random;

/ 编写程序,生成5个不重复的随机数[0-100]。重复的话重新生成。 最终生成的5个随机数放到数组中,要求数组中这5个随机数不重复。 / public class RandomTest02 { public static void main(String[] args) {

  1. // 创建Random对象
  2. Random random = new Random();
  3. // 准备一个长度为5的一维数组。
  4. int[] arr = new int[5]; // 默认值都是0
  5. for(int i = 0; i < arr.length; i++){
  6. arr[i] = -1;
  7. }
  8. // 循环,生成随机数
  9. int index = 0;
  10. while(index < arr.length){
  11. // 生成随机数
  12. //int num = random.nextInt(101);
  13. //int num = random.nextInt(6); // 只能生成[0-5]的随机数!
  14. int num = random.nextInt(4); // 只能生成[0-3]的随机数!永远都有重复的,永远都凑不够5个。
  15. System.out.println("生成的随机数:" + num);
  16. // 判断arr数组中有没有这个num
  17. // 如果没有这个num,就放进去。
  18. if(!contains(arr, num)){
  19. arr[index++] = num;
  20. }
  21. }
  22. // 遍历以上的数组
  23. for(int i = 0; i < arr.length; i++){
  24. System.out.println(arr[i]);
  25. }
  26. }
  27. /**
  28. * 单独编写一个方法,这个方法专门用来判断数组中是否包含某个元素
  29. * @param arr 数组
  30. * @param key 元素
  31. * @return true表示包含,false表示不包含。
  32. */
  33. public static boolean contains(int[] arr, int key){
  34. /*
  35. // 这个方案bug。(排序出问题了。)
  36. // 对数组进行升序
  37. //Arrays.sort(arr);
  38. // 进行二分法查找
  39. // 二分法查找的结果 >= 0说明,这个元素找到了,找到了表示存在!
  40. //return Arrays.binarySearch(arr, key) >= 0;
  41. */
  42. for(int i = 0; i < arr.length; i++){
  43. if(arr[i] == key){
  44. // 条件成立了表示包含,返回true
  45. return true;
  46. }
  47. }
  48. // 这个就表示不包含!
  49. return false;
  50. }

}

  1. <a name="ULhnX"></a>
  2. ### Enum类(枚举)
  3. 总结:<br />1、**枚举是一种引用数据类型**
  4. 2、枚举类型怎么定义,语法是?<br /> enum 枚举类型名{<br /> 枚举值1,枚举值2<br /> }
  5. 3、结果只有两种情况的,建议使用布尔类型。<br /> 结果超过两种并且还是可以一枚一枚列举出来的,建议使用枚举类型。<br /> 例如:颜色、四季、星期等都可以使用枚举类型。
  6. 4、枚举中的每一个值都是常量
  7. <a name="KUWfs"></a>
  8. #### 包名的说明
  9. package com.bjpowernode.javase.enum2; <br />enum是关键字 .后面的功能名字是 标识符,关键字不能做标识符。
  10. <a name="GbMv7"></a>
  11. #### 案例说明枚举
  12. 1. 这个案例没有使用java中的枚举,分析以下程序,在设计方面有什么缺陷?
  13. ```java
  14. public class EnumTest01 {
  15. public static void main(String[] args) {
  16. //System.out.println(10 / 0); //java.lang.ArithmeticException: / by zero
  17. /*
  18. int retValue = divide(10, 2);
  19. System.out.println(retValue == 1 ? "计算成功" : "计算失败"); // 1
  20. int retValue2 = divide(10, 0);
  21. System.out.println(retValue2 == 0 ? "计算失败" : "计算成功"); // 0
  22. */
  23. boolean success = divide(10, 0);
  24. System.out.println(success ? "计算成功" : "计算失败");
  25. }
  26. /**
  27. * 需求(这是设计者说的!):以下程序,计算两个int类型数据的商,计算成功返回1,计算失败返回0
  28. * @param a int类型的数据
  29. * @param b int类型的数据
  30. * @return 返回1表示成功,返回0表示失败!
  31. */
  32. /*
  33. public static int divide(int a, int b){
  34. try {
  35. int c = a / b;
  36. // 程序执行到此处表示以上代码没有发生异常。表示执行成功!
  37. return 1;
  38. } catch (Exception e){
  39. // 程序执行到此处表示以上程序出现了异常!
  40. // 表示执行失败!
  41. return 0;
  42. }
  43. }
  44. */
  45. // 设计缺陷?在这个方法的返回值类型上。返回一个int不恰当。
  46. // 既然最后的结果只是成功和失败,最好使用布尔类型。因为布尔类型true和false正好可以表示两种不同的状态。
  47. /*
  48. public static int divide(int a, int b){
  49. try {
  50. int c = a / b;
  51. // 返回10已经偏离了需求,实际上已经出错了,但是编译器没有检查出来。
  52. // 我们一直想追求的是:所有的错误尽可能让编译器找出来,所有的错误越早发现越好!
  53. return 10;
  54. } catch (Exception e){
  55. return 0;
  56. }
  57. }
  58. */
  59. // 这种设计不错。
  60. public static boolean divide(int a, int b){
  61. try {
  62. int c = a / b;
  63. return true;
  64. } catch (Exception e){
  65. return false;
  66. }
  67. }
  68. /*
  69. 思考:以上的这个方法设计没毛病,挺好,返回true和false表示两种情况,
  70. 但是在以后的开发中,有可能遇到一个方法的执行结果可能包括三种情况,
  71. 四种情况,五种情况不等,但是每一个都是可以数清楚的,一枚一枚都是可以
  72. 列举出来的。这个布尔类型就无法满足需求了。此时需要使用java语言中的
  73. 枚举类型。
  74. */
  75. }
  1. 采用枚举的方式改造程序 ```java public class EnumTest02 { public static void main(String[] args) {

    1. Result r = divide(10, 2);
    2. System.out.println(r == Result.SUCCESS ? "计算成功" : "计算失败");

    }

    /**

    • 计算两个int类型数据的商。
    • @param a int数据
    • @param b int数据
    • @return Result.SUCCESS表示成功,Result.FAIL表示失败! */ public static Result divide(int a, int b){ try {
      1. int c = a / b;
      2. return Result.SUCCESS;
      } catch (Exception e){
      1. return Result.FAIL;
      } } }

// 枚举:一枚一枚可以列举出来的,才建议使用枚举类型。 // 枚举编译之后也是生成class文件。 // 枚举也是一种引用数据类型。 // 枚举中的每一个值可以看做是常量。 enum Result{ // SUCCESS 是枚举Result类型中的一个值 // FAIL 是枚举Result类型中的一个值 // 枚举中的每一个值,可以看做是“常量” SUCCESS, FAIL }

  1. 3. switch语句使用枚举
  2. ```java
  3. public class SwitchTest {
  4. public static void main(String[] args) {
  5. // switch语句支持枚举类型
  6. // switch也支持String、int
  7. // 低版本的JDK,只支持int
  8. // 高版本的JDK,支持int、String、枚举。
  9. // byte short char也可以,因为存在自动类型转换。
  10. switch (Season.SPRING) {
  11. // 必须省略Season.
  12. case SPRING:
  13. System.out.println("春天");
  14. break;
  15. case SUMMER:
  16. System.out.println("夏天");
  17. break;
  18. case AUTUMN:
  19. System.out.println("秋天");
  20. break;
  21. case WINTER:
  22. System.out.println("冬天");
  23. break;
  24. }
  25. }
  26. }
  1. 四季枚举的案例 ```java public enum Season { / 春夏秋冬 / SPRING, SUMMER, AUTUMN, WINTER }
  1. ---
  2. DAy27 index 从以上内容 数组到常用类还未掌握
  3. ---
  4. <a name="d4X3x"></a>
  5. ## 异常类 chapter21
  6. 异常:Exception
  7. <a name="UfWSm"></a>
  8. #### 经典异常
  9. 空指针异常:NullPointerException<br />类型转换异常:ClassCastException<br />数组下标越界异常:ArrayIndexOutOfBoundsException<br />数字格式化异常:NumberFormatException
  10. <a name="J9DbN"></a>
  11. #### 异常
  12. 1、什么是异常,java提供异常处理机制有什么用?<br /> 以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常<br /> java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况,java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加的健壮。
  13. 什么是异常:程序执行过程中的不正常情况。<br /> 异常的作用:增强程序的健壮性。
  14. 2、以下程序执行控制台出现了:<br /> Exception in thread "main" java.lang.ArithmeticException: / by zero<br />at com.bjpowernode.javase.exception.ExceptionTest01.main(ExceptionTest01.java:14)<br /> **这个信息被我们称为:异常信息。这个信息是JVM打印的。**<br />**val**
  15. ```java
  16. public class ExceptionTest01 {
  17. public static void main(String[] args) {
  18. int a = 10;
  19. int b = 0;
  20. // 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
  21. // 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
  22. int c = a / b;
  23. System.out.println(a + "/" + b + "=" + c);

此处运行也会创建一个:ArithmeticException类型的异常对象。
我观察到异常信息之后,对程序进行修改,更加健壮。

  1. System.out.println(100 / 0);
  1. java语言中异常是以什么形式存在的呢?


1、异常在java中以类的形式存在,每一个异常类都可以创建异常对象。

  1. 2、异常对应的现实生活中是怎样的?<br /> 火灾(异常类):<br /> 200888日,小明家着火了(异常对象)<br /> 200889日,小刚家着火了(异常对象)<br /> 200898日,小红家着火了(异常对象)
  2. 类是:模板。<br /> 对象是:实际存在的个体。
  3. 钱包丢了(异常类):<br /> 200818日,小明的钱包丢了(异常对象)<br /> 200819日,小芳的钱包丢了(异常对象)<br /> <br />3、通过实例化创建异常对象
  1. public class ExceptionTest02 {
  2. public static void main(String[] args) {
  3. // 通过“异常类”实例化“异常对象”
  4. NumberFormatException nfe = new NumberFormatException("数字格式化异常!");
  5. // java.lang.NumberFormatException: 数字格式化异常!
  6. System.out.println(nfe);
  7. // 通过“异常类”创建“异常对象”
  8. NullPointerException npe = new NullPointerException("空指针异常发生了!");
  9. //java.lang.NullPointerException: 空指针异常发生了!
  10. System.out.println(npe);
  11. }
  12. }
  1. 分析以下程序

对第四行代码的分析:
程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象,然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方,main方法没有处理,将这个异常自动抛给了JVM,JVM最终终止程序的执行。

  1. ArithmeticException 继承 RuntimeException,属于运行时异常。<br /> 在编写程序阶段不需要对这种异常进行预先的处理。<br />
  1. public class ExceptionTest03 {
  2. public static void main(String[] args) {
  3. System.out.println(100 / 0);
  4. // 这里的HelloWorld没有输出,没有执行。
  5. System.out.println("Hello World!");
  6. }
  7. }
  1. 分析以下代码报错的原因

因为doSome()方法声明位置上使用了:throws ClassNotFoundException,而 ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理编译器报错。

  1. public class ExceptionTest04 {
  2. public static void main(String[] args) {
  3. // main方法中调用doSome()方法
  4. // 因为doSome()方法声明位置上有:throws ClassNotFoundException
  5. // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
  6. // 如果不处理,编译器就报错。
  7. //编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
  8. //doSome();
  9. }
  10. /**
  11. * doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
  12. * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
  13. * 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
  14. * @throws ClassNotFoundException
  15. */
  16. public static void doSome() throws ClassNotFoundException{
  17. System.out.println("doSome!!!!");
  18. }
  19. }
  1. 编译时异常和运行时异常的区别?

    1. 编译时异常一般发生的概率比较高。<br /> 举个例子:<br /> 你看到外面下雨了,倾盆大雨的。<br /> 你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。<br /> 而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。<br /> “拿一把伞”就是对“生病异常”发生之前的一种处理方式。
    2. 对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
    3. 运行时异常一般发生的概率比较低。<br /> 举个例子:<br /> 小明走在大街上,可能会被天上的飞机轮子砸到。<br /> 被飞机轮子砸到也算一种异常。<br /> 但是这种异常发生概率较低。<br /> 在出门之前你没必要提前对这种发生概率较低的异常进行预处理。<br /> 如果你预处理这种异常,你将活的很累。<br /> <br /> 假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加<br /> 的安全,但是你这个人活的很累。<br /> <br /> 假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,<br /> 所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?<br /> 首先,如果这样的话,程序肯定是绝对的安全的。<br /> 但是程序员编写程序太累,代码到处都是处理异常<br /> 的代码。
  2. 编译时异常还有其他名字:

    1. 受检异常:CheckedException<br /> 受控异常<br /> <br /> 运行时异常还有其它名字:<br /> 未受检异常:UnCheckedException<br /> 非受控异常<br /> <br /> 再次强调:所有异常都是发生在运行阶段的。

UML

  1. 工具的介绍

UML是一种统一建模语言。
一种图标式语言(画图的)
UML不是只有java中使用。只要是面向对象的编程语言,都有UML。
一般画UML图的都是软件架构师或者说是系统分析师。这些级别的人员使用的。
软件设计人员使用UML。

  1. UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等.
  2. 盖大楼和软件开发一样,一个道理。<br /> 盖楼之前,会先由建筑师画图纸。图纸上一个一个符号都是标准符号。<br /> 这个图纸画完,只要是搞建筑的都能看懂,因为这个图纸上标注的这些<br /> 符号都是一种“标准的语言”。<br /> <br /> java软件开发当中,软件分析师/设计师负责设计类,java软件开发人员<br /> 必须要能看懂。
  1. 异常在java中以类和对象的形式存在。那么异常的继承结构是怎样的?

    我们可以使用UML图来描述一下继承结构。
    画UML图有很多工具,例如:Rational Rose(收费的)、starUML等….
    Object
    Object下有Throwable(可抛出的)

    1. Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
    2. Exception下有两个分支:<br /> Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。<br /> RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)<br />

    异常的继承结构图

    image.png

    异常的处理方式

Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。

  1. 第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的继续上抛。抛给调用者。
    上抛类似于推卸责任。(继续把异常传递给调用者。)

  2. 第二种处理方式:try..catch进行捕捉。
    // 捕捉等于把异常拦下了,异常真正的解决了。(调用者是不知道的。)

  3. 案例: ```java public class ExceptionTest05 {

    public static void main(String[] args) throws ClassNotFoundException {

    1. doSome();

    }

  1. public static void main(String[] args) {
  2. try {
  3. doSome();
  4. } catch (ClassNotFoundException e) {
  5. e.printStackTrace();
  6. }
  7. }
  1. 4. 异常处理方式的说明:
  2. - 处理异常的第一种方式:<br /> 在方法声明的位置上使用throws关键字抛出,谁调用我这个方法,我就抛给谁。抛给调用者来处理。<br /> 这种处理异常的态度:上报。
  3. - 处理异常的第二种方式:<br /> 使用try..catch语句对异常进行捕捉。<br /> 这个异常不会上报,自己把这个事儿处理了。<br /> 异常抛到此处为止,不再上抛了。
  4. - 注意:<br /> 只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。<br /> 另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。<br /> try..catch捕捉异常之后,后续代码可以执行。
  5. - 在以后的开发中,处理编译时异常,应该上报还是捕捉呢,怎么选?<br /> 如果希望调用者来处理,选择throws上报。<br /> 其它情况使用捕捉的方式。
  6. - 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVMJVM只有终止。异常处理机制的作用就是增强程序的健壮性。怎么能做到,异常发生了也不影响程序的执行。所以 一般main方法中的异常建议使用try..catch进行捕捉。main就不要继续上抛了。
  7. ```java
  8. public static void main(String[] args) throws FileNotFoundException {
  9. System.out.println("main begin");
  10. m1();
  11. System.out.println("main over");
  12. }
  • 100 / 0这是算术异常,这个异常是运行时异常,你在编译阶段,可以处理,也可以不处理。编译器不管。 ```java public static void main(String[] args) {
  1. System.out.println(100 / 0); // 不处理编译器也不管
  2. // 你处理也可以。
  3. /*
  4. try {
  5. System.out.println(100 / 0);
  6. } catch(ArithmeticException e){
  7. System.out.println("算术异常了!!!!");
  8. }
  9. */
  1. ```java
  2. System.out.println("main begin");
  3. try {
  4. // try尝试
  5. m1();
  6. // 以上代码出现异常,直接进入catch语句块中执行。
  7. System.out.println("hello world!");
  8. } catch (FileNotFoundException e){ // catch后面的好像一个方法的形参。
  9. // 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址。
  10. // catch是捕捉异常之后走的分支。
  11. // 在catch分支中干什么?处理异常。
  12. System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!");
  13. System.out.println(e); //java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)
  14. }
  15. // try..catch把异常抓住之后,这里的代码会继续执行。
  16. System.out.println("main over");
  17. }
  1. private static void m1() throws FileNotFoundException {
  2. System.out.println("m1 begin");
  3. m2();
  4. // 以上代码出异常,这里是无法执行的。
  5. System.out.println("m1 over");
  6. }
  1. 抛别的不行,抛ClassCastException说明你还是没有对FileNotFoundException进行处理<br /> //private static void m2() throws ClassCastException{<br /> 抛FileNotFoundException的父对象IOException,这样是可以的。因为IOException包括FileNotFoundException
  2. //private static void m2() throws IOException {<br /> 这样也可以,因为Exception包括所有的异常。
  3. //private static void m2() throws Exception{<br /> throws后面也可以写多个异常,可以使用逗号隔开。<br /> //private static void m2() throws ClassCastException, FileNotFoundException{
  1. private static void m2() throws FileNotFoundException {
  2. System.out.println("m2 begin");
  3. // 编译器报错原因是:m3()方法声明位置上有:throws FileNotFoundException
  4. // 我们在这里调用m3()没有对异常进行预处理,所以编译报错。
  5. // m3();
  6. m3();
  7. // 以上如果出现异常,这里是无法执行的!
  8. System.out.println("m2 over");
  9. }

//new FileInputStream(“D:\course\01-开课\学习方法.txt”);
调用SUN jdk中某个类的构造方法。
这个类还没有接触过,后期IO流的时候就知道了。
我们只是借助这个类学习一下异常处理机制。
创建一个输入流对象,该流指向一个文件。

编译报错的原因是什么?
第一:这里调用了一个构造方法:FileInputStream(String name)
第二:这个构造方法的声明位置上有:throws FileNotFoundException
第三:通过类的继承结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,最终得知,FileNotFoundException是编译时异常。

  1. 错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就报错
  1. private static void m3() throws FileNotFoundException {
  2. // 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
  3. // 一个方法体当中的代码出现异常之后,如果上报的话,此方法结束。
  4. new FileInputStream("D:\\course\\01-课\\学习方法.txt");
  5. System.out.println("如果以上代码出异常,这里会执行吗??????????????????不会!!!");
  6. }
  7. }

深入try .. catch

深入try..catch
1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
2、catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
3、catch写多个的时候,从上到下,必须遵守从小到大。

  1. public class ExceptionTest07 {
  2. /*
  3. public static void main(String[] args) throws Exception, FileNotFoundException, NullPointerException {
  4. }
  5. */
  6. /*public static void main(String[] args) throws Exception {
  7. }*/
  8. public static void main(String[] args) {
  9. //编译报错
  10. /*try {
  11. FileInputStream fis = new FileInputStream("D:\\course\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  12. } catch(NullPointerException e) {
  13. }*/
  14. /*try {
  15. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  16. System.out.println("以上出现异常,这里无法执行!");
  17. } catch(FileNotFoundException e) {
  18. System.out.println("文件不存在!");
  19. }
  20. System.out.println("hello world!");*/
  21. /*try {
  22. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  23. } catch(IOException e) { // 多态:IOException e = new FileNotFoundException();
  24. System.out.println("文件不存在!");
  25. }*/
  26. /*try {
  27. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  28. } catch(Exception e) { // 多态:Exception e = new FileNotFoundException();
  29. System.out.println("文件不存在!");
  30. }*/
  31. /*try {
  32. //创建输入流
  33. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  34. //读文件
  35. fis.read();
  36. } catch(Exception e) { //所有的异常都走这个分支。
  37. System.out.println("文件不存在!");
  38. }*/
  39. /*try {
  40. //创建输入流
  41. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  42. //读文件
  43. fis.read();
  44. } catch(FileNotFoundException e) {
  45. System.out.println("文件不存在!");
  46. } catch(IOException e){
  47. System.out.println("读文件报错了!");
  48. }*/
  49. // 编译报错。
  50. /*
  51. try {
  52. //创建输入流
  53. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  54. //读文件
  55. fis.read();
  56. } catch(IOException e){
  57. System.out.println("读文件报错了!");
  58. } catch(FileNotFoundException e) {
  59. System.out.println("文件不存在!");
  60. }
  61. */

catch在JDK8的新特性

catch(FileNotFoundException | ArithmeticException | NullPointerException e)

  1. // JDK8的新特性!
  2. try {
  3. //创建输入流
  4. FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  5. // 进行数学运算
  6. System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
  7. } catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
  8. System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
  9. }
  10. }
  11. }

class 到 java 为 反编译

java到 class 是 编译

异常对象的两个重要方法

  1. 异常对象有两个非常重要的方法:
  • 获取异常简单的描述信息:
    String msg = exception.getMessage();

  • 打印异常追踪的堆栈信息:
    exception.printStackTrace();

  1. ublic class ExceptionTest08 {
  2. public static void main(String[] args) {
  3. // 这里只是为了测试getMessage()方法和printStackTrace()方法。
  4. // 这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。
  5. NullPointerException e = new NullPointerException("空指针异常fdsafdsafdsafds");
  6. // 获取异常简单描述信息:这个信息实际上就是构造方法上面String参数。
  7. String msg = e.getMessage(); //空指针异常fdsafdsafdsafds
  8. System.out.println(msg);
  9. // 打印异常堆栈信息
  10. // java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
  11. e.printStackTrace();
  12. for(int i = 0; i < 1000; i++){
  13. System.out.println("i = " + i);
  14. }
  15. System.out.println("Hello World!");
  16. }
  17. }
  1. 异常对象的两个方法:
    String msg = e.getMessage();
    e.printStackTrace(); // 一般都是使用这个。

我们以后查看异常的追踪信息,我们应该怎么看,可以快速的调试程序呢?
异常信息追踪信息,从上往下一行一行看。
但是需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的。)。
主要的问题是出现在自己编写的代码上。

  1. public class ExceptionTest09 {
  2. public static void main(String[] args) {
  3. try {
  4. m1();
  5. } catch (FileNotFoundException e) {
  6. // 获取异常的简单描述信息
  7. String msg = e.getMessage();
  8. System.out.println(msg); //C:\jetns-agent.jar (系统找不到指定的文件。)
  9. //打印异常堆栈追踪信息!!!
  10. //在实际的开发中,建议使用这个。养成好习惯!
  11. // 这行代码要写上,不然出问题你也不知道!
  12. //e.printStackTrace();
  13. /*
  14. java.io.FileNotFoundException: C:\jetns-agent.jar (系统找不到指定的文件。)
  15. at java.base/java.io.FileInputStream.open0(Native Method)
  16. at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
  17. at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)
  18. at java.base/java.io.FileInputStream.<init>(FileInputStream.java:110)
  19. at com.bjpowernode.javase.exception.ExceptionTest09.m3(ExceptionTest09.java:31)
  20. at com.bjpowernode.javase.exception.ExceptionTest09.m2(ExceptionTest09.java:27)
  21. at com.bjpowernode.javase.exception.ExceptionTest09.m1(ExceptionTest09.java:23)
  22. at com.bjpowernode.javase.exception.ExceptionTest09.main(ExceptionTest09.java:14)
  23. 因为31行出问题导致了27行
  24. 27行出问题导致23行
  25. 23行出问题导致14行。
  26. 应该先查看31行的代码。31行是代码错误的根源。
  27. */
  28. }
  29. // 这里程序不耽误执行,很健壮。《服务器不会因为遇到异常而宕机。》
  30. System.out.println("Hello World!");
  31. }
  32. private static void m1() throws FileNotFoundException {
  33. m2();
  34. }
  35. private static void m2() throws FileNotFoundException {
  36. m3();
  37. }
  38. private static void m3() throws FileNotFoundException {
  39. new FileInputStream("C:\\jetns-agent.jar");
  40. }
  41. }

finally

关于try..catch中的finally子句:
1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。

  1. 2finally语句通常使用在哪些情况下呢?<br /> 通常在finally语句块中完成资源的释放/关闭。<br /> 因为finally中的代码比较有保障。<br /> 即使try语句块中的代码出现异常,finally中代码也会正常执行。
  1. public class ExceptionTest10 {
  2. public static void main(String[] args) {
  3. FileInputStream fis = null; // 声明位置放到try外面。这样在finally中才能用。
  4. try {
  5. // 创建输入流对象
  6. fis = new FileInputStream("D:\\course\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
  7. // 开始读文件....
  8. String s = null;
  9. // 这里一定会出现空指针异常!
  10. s.toString();
  11. System.out.println("hello world!");
  12. // 流使用完需要关闭,因为流是占用资源的。
  13. // 即使以上程序出现异常,流也必须要关闭!
  14. // 放在这里有可能流关不了。
  15. //fis.close();
  16. } catch (FileNotFoundException e) {
  17. e.printStackTrace();
  18. } catch(IOException e){
  19. e.printStackTrace();
  20. } catch(NullPointerException e) {
  21. e.printStackTrace();
  22. } finally {
  23. System.out.println("hello 浩克!");
  24. // 流的关闭放在这里比较保险。
  25. // finally中的代码是一定会执行的。
  26. // 即使try中出现了异常!
  27. if (fis != null) { // 避免空指针异常!
  28. try {
  29. // close()方法有异常,采用捕捉的方式。
  30. fis.close();
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. System.out.println("hello kitty!");
  37. }
  38. }

3、finally语句:
放在finally语句块中的代码是一定会执行的【再次强调!!!】
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。

  1. public class ExceptionTest11 {
  2. public static void main(String[] args) {
  3. /*
  4. try和finally,没有catch可以吗?可以。
  5. try不能单独使用。
  6. try finally可以联合使用。
  7. 以下代码的执行顺序:
  8. 先执行try...
  9. 再执行finally...
  10. 最后执行 return (return语句只要执行方法必然结束。)
  11. */
  12. try {
  13. System.out.println("try...");
  14. return;
  15. } finally {
  16. // finally中的语句会执行。能执行到。
  17. System.out.println("finally...");
  18. }
  19. // 这里不能写语句,因为这个代码是无法执行到的。
  20. //System.out.println("Hello World!");
  21. }
  22. }

4、finally语句不执行情况

  1. finally
  2. */
  3. public class ExceptionTest12 {
  4. public static void main(String[] args) {
  5. try {
  6. System.out.println("try...");
  7. // 退出JVM
  8. System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
  9. } finally {
  10. System.out.println("finally...");
  11. }
  12. }
  13. }

finally面试题

判断以下代码的输出结果

java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):

  1. java中有一条这样的规则:<br /> 方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
  2. java中还有一条语法规则:<br /> return语句一旦执行,整个方法必须结束(亘古不变的语法!)
  1. public class ExceptionTest13 {
  2. public static void main(String[] args) {
  3. int result = m();
  4. System.out.println(result); //100
  5. }
  6. public static int m(){
  7. int i = 100;
  8. try {
  9. // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
  10. // return语句还必须保证是最后执行的。一旦执行,整个方法结束。
  11. return i;
  12. } finally {
  13. i++;
  14. }
  15. }
  16. }
  17. /*
  18. 反编译之后的效果
  19. public static int m(){
  20. int i = 100;
  21. int j = i;
  22. i++;
  23. return j;
  24. }
  25. */

面试题二:

  1. public class Demo {
  2. public static String sRet = "";
  3. public static void func(int i)
  4. {
  5. try
  6. {
  7. if (i%2==0)
  8. {
  9. throw new Exception();
  10. }
  11. }
  12. catch (Exception e)
  13. {
  14. sRet += "0";
  15. return;
  16. }
  17. finally
  18. {
  19. sRet += "1";
  20. }
  21. sRet += "2";
  22. }
  23. public static void main(String[] args)
  24. {
  25. func(1);
  26. func(2);
  27. System.out.println(sRet);
  28. }
  29. }

答案:第一步,func(1),if条件不成立,不抛出异常,catch不运行,final运行,拼串得到“1”,程序继续往下走,拼串得到“12”。 第二步,fun(2),if条件成立,抛出异常,catch捕获异常,运行catch里面代码,拼串得到“120”,虽然有return,但是不管出不出异常,final里代码必须执行,执行final,拼串得到“1201”,然后return结束。所以最终结果“1201”。

final finally finalize有什么区别?
  • final 关键字
    final修饰的类无法继承
    final修饰的方法无法覆盖
    final修饰的变量不能重新赋值。

  • finally 关键字
    和try一起联合使用。
    finally语句块中的代码是必须执行的。

  • finalize 标识符
    是一个Object类中的方法名。
    这个方法是由垃圾回收器GC负责调用的。 ```java public class ExceptionTest14 { public static void main(String[] args) {

    1. // final是一个关键字。表示最终的。不变的。
    2. final int i = 100;
    3. //i = 200;
    4. // finally也是一个关键字,和try联合使用,使用在异常处理机制中
    5. // 在fianlly语句块中的代码是一定会执行的。
    6. try {
    7. } finally {
    8. System.out.println("finally....");
    9. }
    10. // finalize()是Object类中的一个方法。作为方法名出现。
    11. // 所以finalize是标识符。
    12. // finalize()方法是JVM的GC垃圾回收器负责调用。
    13. Object obj;

    } }

// final修饰的类无法继承 final class A { // 常量。 public static final double MATH_PI = 3.1415926; }

class B { // final修饰的方法无法覆盖 public final void doSome(){

  1. }

}

  1. <a name="jAfN6"></a>
  2. #### 自定义创建异常类
  3. 1. 自定义异常类语法
  4. 1、SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。那么异常类我们程序员可以自己定义吗?<br /> 可以。
  5. 2、Java中怎么自定义异常呢?<br /> 两步:<br /> 第一步:编写一个类继承Exception或者RuntimeException.<br /> 第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
  6. ```java
  7. public class MyException extends Exception{ // 编译时异常
  8. public MyException(){
  9. }
  10. public MyException(String s){
  11. super(s);
  12. }
  13. }
  14. /*
  15. public class MyException extends RuntimeException{ // 运行时异常
  16. }
  17. */
  1. public class ExceptionTest15 {
  2. public static void main(String[] args) {
  3. // 创建异常对象(只new了异常对象,并没有手动抛出)
  4. MyException e = new MyException("用户名不能为空!");
  5. // 打印异常堆栈信息
  6. e.printStackTrace();
  7. // 获取异常简单描述信息
  8. String msg = e.getMessage();
  9. System.out.println(msg);
  10. }
  11. }
  1. 实例(改良之前的day23天MyStrack作业)

测试改良之后的MyStack
注意:最后这个例子,是异常最终要的案例。必须掌握。自定义异常在实际开发中的应用。

  1. public class ExceptionTest16 {
  2. public static void main(String[] args) {
  3. // 创建栈对象
  4. MyStack stack = new MyStack();
  5. // 压栈
  6. try {
  7. stack.push(new Object());
  8. stack.push(new Object());
  9. stack.push(new Object());
  10. stack.push(new Object());
  11. stack.push(new Object());
  12. stack.push(new Object());
  13. stack.push(new Object());
  14. stack.push(new Object());
  15. stack.push(new Object());
  16. stack.push(new Object());
  17. // 这里栈满了
  18. stack.push(new Object());
  19. } catch (MyStackOperationException e) {
  20. // 输出异常的简单信息。
  21. System.out.println(e.getMessage());
  22. }
  23. // 弹栈
  24. try {
  25. stack.pop();
  26. stack.pop();
  27. stack.pop();
  28. stack.pop();
  29. stack.pop();
  30. stack.pop();
  31. stack.pop();
  32. stack.pop();
  33. stack.pop();
  34. stack.pop();
  35. // 弹栈失败
  36. stack.pop();
  37. } catch (MyStackOperationException e) {
  38. System.out.println(e.getMessage());
  39. }
  40. }
  41. }

之前写的程序

  1. *
  2. 编写程序,使用一维数组,模拟栈数据结构。
  3. 要求:
  4. 1、这个栈可以存储java中的任何引用类型的数据。
  5. 2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息。)
  6. 3、在栈中提供pop方法模拟弹栈。(栈空了,也有有提示信息。)
  7. 4、编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作。
  8. 5、假设栈的默认初始化容量是10.(请注意无参数构造方法的编写方式。)
  9. */
  10. public class MyStack {
  11. // 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。
  12. // 因为数组是我们学习java的第一个容器。
  13. // 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据
  14. // new Animal()对象可以放进去,new Person()对象也可以放进去。因为Animal和Person的超级父类就是Object。
  15. // 包括String也可以存储进去。因为String父类也是Object。
  16. private Object[] elements;
  17. // 栈帧,永远指向栈顶部元素
  18. // 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。
  19. //private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。
  20. //private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。
  21. private int index;
  22. /**
  23. * 无参数构造方法。默认初始化栈容量10.
  24. */
  25. public MyStack() {
  26. // 一维数组动态初始化
  27. // 默认初始化容量是10.
  28. this.elements = new Object[10];
  29. // 给index初始化
  30. this.index = -1;
  31. }
  32. /**
  33. * 压栈的方法
  34. * @param obj 被压入的元素
  35. */
  36. public void push(Object obj) throws MyStackOperationException {
  37. if(index >= elements.length - 1){
  38. // 改良之前
  39. //System.out.println("压栈失败,栈已满!");
  40. //return;
  41. // 创建异常对象
  42. //MyStackOperationException e = new MyStackOperationException("压栈失败,栈已满!");
  43. // 手动将异常抛出去!
  44. //throw e; //这里捕捉没有意义,自己new一个异常,自己捉,没有意义。栈已满这个信息你需要传递出去。
  45. // 合并(手动抛出异常!)
  46. throw new MyStackOperationException("压栈失败,栈已满!");
  47. }
  48. // 程序能够走到这里,说明栈没满
  49. // 向栈中加1个元素,栈帧向上移动一个位置。
  50. index++;
  51. elements[index] = obj;
  52. // 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。
  53. System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
  54. }
  55. /**
  56. * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
  57. * @return
  58. */
  59. public void pop() throws MyStackOperationException {
  60. if(index < 0){
  61. //System.out.println("弹栈失败,栈已空!");
  62. //return;
  63. throw new MyStackOperationException("弹栈失败,栈已空!");
  64. }
  65. // 程序能够执行到此处说明栈没有空。
  66. System.out.print("弹栈" + elements[index] + "元素成功,");
  67. // 栈帧向下移动一位。
  68. index--;
  69. System.out.println("栈帧指向" + index);
  70. }
  71. // set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。
  72. // 封装:第一步:属性私有化,第二步:对外提供set和get方法。
  73. public Object[] getElements() {
  74. return elements;
  75. }
  76. public void setElements(Object[] elements) {
  77. this.elements = elements;
  78. }
  79. public int getIndex() {
  80. return index;
  81. }
  82. public void setIndex(int index) {
  83. this.index = index;
  84. }
  85. }

栈操作异常:自定义异常

  1. public class MyStackOperationException extends Exception{ // 编译时异常!
  2. public MyStackOperationException(){
  3. }
  4. public MyStackOperationException(String s){
  5. super(s);
  6. }
  7. }

方法覆盖

  1. 之前在讲解方法覆盖的时候,当时遗留了一个问题?
    重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。 ```java class Animal { public void doSome(){

    }

    public void doOther() throws Exception{

    } }

class Cat extends Animal {

  1. // 编译正常。
  2. public void doSome() throws RuntimeException{
  3. }
  4. // 编译报错。
  5. /*public void doSome() throws Exception{
  6. }*/
  7. // 编译正常。
  8. /*public void doOther() {
  9. }*/
  10. // 编译正常。
  11. /*public void doOther() throws Exception{
  12. }*/
  13. // 编译正常。
  14. public void doOther() throws NullPointerException{
  15. }

}

```


总结异常中出现的关键字

异常捕捉:
try
catch
finally

throws 在方法声明位置上使用,表示上报异常信息给调用者。
throw 手动抛出异常!

Day27天作业题目