Return Java
- 第九章
- 类变量与类方法
- 第十章
- 第十一章
- 第十二章
- 第十三章
第九章
类变量与类方法
9.1 类变量
9.1.1 类变量的内存刨析:
static变量保存在class实例的尾部,
JDK7以上的版本,静态域储存于定义类型的Class对象中,Class对象如同队中其他对象一样,存在于GC堆中。
基本共识:
1、static变量是同一个类所有对象的共享
2、static类变量,在类加载的时候就生成了。
9.1.2类变量介绍
类变量概念:
类变量也叫做静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都i是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量,从上面的内存刨析图可以看出。
类变量定义:
访问修饰符 static 数据类型 变量名;{推荐使用}
static 访问修饰符 数据类型 变量名;
类变量的访问:
类名.类变量名{推荐使用}
对象名.类变量名 【静态变量的访问修饰符的访问权限和范围 与 普通属性一样的】
public class ChildGame {
public static void main(String[] args) {
//类名.类变量名
//因为static是随着类的加载而创建的,所以即使没有创建对象实例也可以用类名去访问
System.out.println(A.name);
//对象名.类变量名
A a = new A();
System.out.println(a.name);
}
}
class A{
//类变量的访问也要遵循访问修饰符的访问权限
//如果name的访问修饰符是privat,则在上面的类中的访问方法就会报错
public static String name = "猪猪";
}
9.1.3 类变量的细节
1、什么时候使用类变量
当我们需要让某个类的所有对象都共享一个变量的时候,可以考虑使用类变量(静态变量),比如:定义学生类,统计所有学生交多少钱的时候。Student(name,static fee)
2、类变量与实例变量(普通变量)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
3、加上static称为类变量或者静态变量,否则称为实例变量/普通变量/非静态变量
4、类变量的访问可以通过: 类名.类变量名【推荐使用】 或者 对象名.类变量名
5、实例变量不能通过 类名.类变量名 方式访问
6、类变量是在类加载的时候就初始化了,也就是说,即使没有创建对象,只要是类以及加载了,就可以使用类变量。
7、类变量的生命周期是随类的加载开始,随着类的消亡而销毁
public class ChildGame {
public static void main(String[] args) {
//类名.类变量名
//因为static是随着类的加载而创建的,所以即使没有创建对象实例也可以用类名去访问
System.out.println(A.name);
//因为age是实例变量所以不能用 类名.类变量名访问,所以会报错
System.out.println(A.age);//错的
//对象名.类变量名
A a = new A();
System.out.println(a.name);
}
}
class A{
//类变量的访问也要遵循访问修饰符的访问权限
//如果name的访问修饰符是privat,则在上面的类中的访问方法就会报错
public static String name = "猪猪";
//实例变量/普通变量/
public int age = 10;
}
9.2类方法
9.2.1 类方法基本介绍
类方法又叫做静态方法
类方法定义形式:
访问修饰符 static 数据返回类型 方法名(){ }【推荐使用】
static 访问修饰符 数据返回类型 方法名(){ }
类方法的调用:
类名.类方法名 或者 对象名.类方法名
public class StaticMethod {
public static void main(String[] args) {
//创建2个学生对象,交学费
Student tom = new Student("tom");
tom.payFee(100);//用对象名.类方法名 调用
//Student.payFee(100);//用类名.类方法名 调用
Student jack = new Student("jack");
jack.payFee(200);
//Student.payFee(200);
Student.showFee();//用类名去调用显示费用的方法,因为fee是定义的一个类变量,
//所以每一次调用的时候,调用的都是同一个地址的类变量,所以每一次调用,值都会变化
}
}
class Student{
private String name;//普通成员
//定义一个静态变量,来累积学生的学费
private static double fee = 0;
public Student(String name) {
this.name = name;
}
//1、当方法使用static的时候,表示该方法就是静态方法
//2、静态放啊发可以访问静态变量
public static void payFee(double fee){
Student.fee += fee;//累积到静态变量fee中
}
public static void showFee(){
System.out.println("总学费="+Student.fee);
}
}
9.2.2 类方法使用场景
1、当方法中不涉及任何与对象相关的成员,则可以将方法设计成静态方法,提高开发效率。比如工具类中的方法 utils、Math类、Arrays类等等:以下是其Math源码:
2、当程序员自己开发中,通常会将一些通用的方法,去设计成静态方法,这样我们不需要创建对象就可以直接使用的工具方法,例如打印一维数组、冒泡排序,完成某个计算任务等
System.out.println(Student.sum(20,30));
}
}
class Student{//获取两个数的和的方法,将其定义成一个static方法,然后可以直接调用
public static double sum(double n1,double n2){
return n1+n2;
}
9.2.3 类方法的细节
1、类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
注:类方法 中没有this的参数; 普通放啊发中隐含有this参数
2、类方法 可以通过类名调用,也可以通过对象名调用
3、普通方法和对象有关,需要通过对象名调用,比如: 对象名.方法名(参数),不能通过类名调用
4、类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
5、类方法(静态方法)中 只能访问 静态变量 或者静态变量
6、普通成员方法,既可以访问普通方法(方法),也可以访问静态变量(方法)
总结:静态方法(类方法) 只能访问静态成员和静态方法,非静态的方法(普通方法),可以访问静态成员和非静态成员(必须遵守访问权限)
9.3 main语法
9.3.1main语法说明
main方法的形式:
public static void main(String[ ] args){ }
注:main方法是由虚拟机去调用的
解释:
1、public 是因为当Java虚拟机要去调用main()方法的时候,必须要由访问权限,所以必须是public公共的
2、static 是因为 当Java虚拟机去调用的时候,是不需要创建对象的,所以将其定义成一个静态方法,可以直接被Java虚拟机调用
3、main()方法接收的是String类型的数组参数,该数组中保存执行Java命令时,传递给所运行的类的参数
4、String[ ] 数组的参数值,是在 Java 执行程序的时候所传进去的 在命令行中
例: Java 执行程序 参数1 参数2 参数3
9.3.2 main方法特别说明
1、在main()方法中。我们可以直接调用main方法所在类的静态方法或者静态属性
2、但是,不能直接访问该类中的非静态成员,必须要创建一个对象后,才可以通过对象去访问类中的非静态成员。毕竟 main()方法是一个静态方法
代码:
public class Main01 {
//静态变量/成员
private static String name = "小王";
//静态方法
public static void hi(){
System.out.println("我是静态方法hi");
}
//普通变量/成员
private int age = 21;
//普通方法
public void ok(){
System.out.println("我是普通方法ok");
}
public static void main(String[] args) {
//因为main方法时静态的,所以可以访问静态变量、静态方法
System.out.println("name= "+name);
hi();
//访问普通变量
// System.out.println("age = "+age);//错误
//访问普通方法
// ok();//错误
//因为main()方法是静态方法,所以不能直接访问该类的非静态成员
//所以直接会报错,要访问必须创建一个类对象,用对象去调用访问
Main01 main01 = new Main01();
System.out.println("age= "+main01.age);
main01.ok();
}
}
输出:
9.3.3 main方法动态传参(idea)
在 9.3.1 中的 4 那里,用的是命令行去传参的,而在平时的工作或者开发过程中都是用工具(idea、eclipse等),所以我们看一下在idea中,如何去传参
首先先输入输出的代码:
public class Main02 {
public static void main(String[] args) {
for (int i = 0; i < args.length ; i++) {
System.out.println("arg["+ i +"]= " + args[i]);
}
}
}
当直接运行这段代码的时候,当然不会输出任何值,传参方式如下:
输出:
9.4 代码块
9.4.1 代码块基本介绍
基本概念:
代码化块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{ }包装起来,
但是,和方法又不同,代码块没有方法名,没有返回,没有参数,只有方法体(也就是 只有方法体的方法代码块),而且不用通过对象或者类去显式调用,而是在加载类的时候,或者创建对象的时候去隐式调用的
基本语法:
[修饰符] {
代码
};
注:
1、修饰符 是可以选择的 , 要写的话,也只能写static
2、代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的是普通代码块/非静态代码块
3、逻辑语句可以为任何的逻辑语句(输入、输出、方法调用、循环、判断等)
4、 ;号可以写也可以省略
9.4.2代码块的使用
1、相当于另外一种形式的构造器(对构造器的补充机制),可以作初始化的操作
2、使用场景:如果多个构造器中都由重复的语句,可以抽取到初始化块中,提高代码的重用性。
案例:
创建一个电影类,属性如下:创建三个构造器,当创建一个对象的时候,无论使用那个构造器都会输出以下三句话:
System.out.println(“电影名字:”+name);
System.out.println(“电影价格:”+price);
System.out.println(“电影导演:”+actor);
第一种方法:
直接在每一个构造器里面去输出三句话。
public class Block01 {
public static void main(String[] args) {
Movie movie = new Movie("反贪5");
}
}
class Movie{
private String name;
private int price;
private String actor;
public Movie(String name) {
System.out.println("电影名字:"+name);
System.out.println("电影价格:"+price);
System.out.println("电影导演:"+actor);
this.name = name;
}
public Movie(String name, int price) {
System.out.println("电影名字:"+name);
System.out.println("电影价格:"+price);
System.out.println("电影导演:"+actor);
this.name = name;
this.price = price;
}
public Movie(String name, int price, String actor) {
System.out.println("电影名字:"+name);
System.out.println("电影价格:"+price);
System.out.println("电影导演:"+actor);
this.name = name;
this.price = price;
this.actor = actor;
}
}
这种方法可以在每一个构造器中去输出这三句话,但是随着构造器越多,代码的复杂度就月大,看起来就很不清晰。
第二种方法:
鉴于避免以上的代码冗余,所以我们可以将其共有部分放在代码块中,当创建对象的时候,就会自动的调用代码块。
public class Block01 {
public static void main(String[] args) {
Movie movie = new Movie("反贪5");
Movie movie1 = new Movie("你好,李焕英", 40, "weizhi");
}
}
class Movie{
private String name;
private int price;
private String actor;
{
System.out.println("电影屏幕打开");
System.out.println("播放广告");
System.out.println("开始播放电影");
};
public Movie(String name) {
System.out.println("Movie(String name)构造器被调用");
this.name = name;
System.out.println(toString());
System.out.println("========================");
}
public Movie(String name, int price) {
System.out.println("Movie(String name, int price)构造器被调用");
this.name = name;
this.price = price;
System.out.println(toString());
}
public Movie(String name, int price, String actor) {
System.out.println("Movie(String name, int price, String actor)构造器被调用");
this.name = name;
this.price = price;
this.actor = actor;
System.out.println(toString());
}
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", price=" + price +
", actor='" + actor + '\'' +
'}';
}
}
输出:
电影屏幕打开
播放广告
开始播放电影
Movie(String name)构造器被调用
Movie{name='反贪5', price=0, actor='null'}
========================
电影屏幕打开
播放广告
开始播放电影
Movie(String name, int price, String actor)构造器被调用
Movie{name='你好,李焕英', price=40, actor='weizhi'}
由这个方法我们可以看出,当一个对象被创建的时候,代码块将会被执行,由输出我们可以看出,代码块的内容被优先于调用,
通过两种方法的比较,我们可以看出,使用代码块,可以提高代码的复用性,以及可以减少程序的复杂度和清晰度。
9.4.3 注意事项和细节!!
1、static代码块也叫静态代码块,作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通的代码块,则每创建一个对象就执行。
2、类什么时候被加载[重要]!!!!!!
①创建对象实例时(new 一个对象的时候)
②创建子类对象实例时,父类也会被加载(由下面案例可知,先加载父类再加载子类)
③使用类的静态成员时(静态属性,静态方法)
(通过输出语句来判断类是否被加载,以下是 static 代码块演示):
public class Block02 {
public static void main(String[] args) {
//1.创建实例对象时(new)
AA aa = new AA();//执行一次
//2.创建子类对象实例时,父类也会被加载(先加载父类,再加载子类,所以先输出BB,再输出AA)
AA aa2 = new AA();//因为static代码块只会被执行一次,所以在这里不会再被执行
//3.使用类的静态成员时(静态属性,静态方法)
System.out.println(Cat.age);//(先加载父类,再加载子类)
}
}
class Animal{
static {
System.out.println("Animal的静态代码块1被执行");
}
}
class Cat extens Animal{
public static int age = 3;
static {
System.out.println("Cat的静态代码块1被执行");
}
}
class AA extends BB{
//静态代码块
static {
System.out.println("AA的静态代码块1被执行");
}
}
class BB{
static {
System.out.println("BB的静态代码块1被执行");
}
}
输出:
BB的静态代码块1被执行
AA的静态代码块1被执行
Animal的静态代码块1被执行
Cat的静态代码块1被执行
3
3、普通代码块,在创建对象实例时,会被隐式调用,每被创建一次就被调用一次
public class Block02 {
public static void main(String[] args) {
DD dd = new DD();
DD dd1 = new DD();//静态代码块只执行一次,普通的创建一个对象就执行一次
}
}
class DD{
public static int n1 = 222;
static {
//静态代码块
System.out.println("DD的静态代码块1被执行");
}
{
//普通代码块
System.out.println("DD的普通代码块被执行");
}
}
输出:
DD的静态代码块1被执行
DD的普通代码块被执行
DD的普通代码块被执行
如果是只使用类的静态成员,则普通代码块不会被执行(因为普通代码块是构造器的补充,如果构造器被调用(new 一个对象),则普通代码块会被执行)
public class Block02 {
public static void main(String[] args) {
System.out.println(DD.n1);//静态代码块一定会被输出,
// 普通代码块不会执行,创建对象了(new一个对象)才能被执行,每创建一个对象就会被调用一次
}
}
class DD{
public static int n1 = 222;
static {
//静态代码块
System.out.println("DD的静态代码块1被执行");
}
{
//普通代码块
System.out.println("DD的普通代码块被执行");
}
}
输出:
DD的静态代码块1被执行
222
注:
1)、static代码块是类加载时,执行,只会被执行一次
2)、普通代码块是在创建对象时调用的,创建一次,调用一次
3)、类加载的三种情况,要记住
4、创建一个对象时,在一个类中的调用顺序(重点、难点!!!)
①调用静态代码块和静态属性初始化(注:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)主要是因为是类加载时被调用
public class Block03 {
public static void main(String[] args) {
A a = new A();//调用顺序:(1)getN1被调用(2)A 静态代码块01,
//static的优先级一样,所以按照定义顺序去调用
}
}
class A{
//静态属性初始化
private static int n1=getN1();
static {//静态代码块
System.out.println("A 静态代码块01");
}
public static int getN1(){
System.out.println("getN1被调用");
return 100;
}
}
输出:
getN1被调用
A 静态代码块01
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果由多个普通代码块和多个普通属性初始化,则按定义顺序调用)是用对象去加载
public class Block03 {
public static void main(String[] args) {
A a = new A();//调用顺序:(1)getN2被调用(2)A 的普通代码块01
//普通代码块的优先级一样,所以按照定义顺序去调用
}
}
class A{
private int n2 = getN2();//普通属性初始化
{
System.out.println("A 的普通代码块01");
}
public int getN2(){//普通方法/非静态方法
System.out.println("getN2被调用");
return 200;
}
}
输出:
getN2被调用
A 的普通代码块01
③最后再调用构造器(综合代码)
public class Block03 {
public static void main(String[] args) {
A a = new A();//调用顺序:(1)getN1被调用(2)A 静态代码块01(3)getN2被调用(4)A 的普通代码块01(5)A的无参构造器被调用
//static的优先级一样,所以按照定义顺序去调用
}
}
class A{
//构造器,最后被调用
public A() {
System.out.println("A的无参构造器被调用");
}
//普通属性初始化
private int n2 = getN2();
//普通代码块
{
System.out.println("A 的普通代码块01");
}
public int getN2(){//普通方法/非静态方法
System.out.println("getN2被调用");
return 200;
}
//静态属性初始化
private static int n1=getN1();
static {//静态代码块
System.out.println("A 静态代码块01");
}
public static int getN1(){
System.out.println("getN1被调用");
return 100;
}
}
输出:
getN1被调用
A 静态代码块01
getN2被调用
A 的普通代码块01
A的无参构造器被调用
总结:当在 “一个类” 中创建一个对象实例的时候,调用顺序是:静态(按定义顺序)>非静态(按定义顺序)>构造器,静态是与类
5、构造器 的最前面其实隐含了 super()和 调用普通代码块,静态(static)相关的代码块,属性初始化,在类加载时,就i执行完毕,因此是优先于 构造器和普通代码块的
class A{
public A(){
//这里由隐藏的执行要求
//(1)super()
//(2)调用本类的普通代码块
}
}
public class Block04 {
public static void main(String[] args) {
new B();
}
}
class C{
{
System.out.println("C的普通代码块");
}
public C(){
//这里其实是隐藏了两个方法的
//(1)super();
//(2)调用本类的普通代码块
System.out.println("C的无参构造器");
}
}
class B extends C{
{//普通代码块
System.out.println("B的普通代码块");
}
private static String n1 =getN1();//静态属性初始化
public static String getN1(){
System.out.println("B的static的n1被调用");//静态代码块在对象加载的时候就被调用,所以先输出
return n1;
}
public B(){
//这里其实是隐藏了两个方法的
//(1)super();
//(2)调用本类的普通代码块
System.out.println("B的无参构造器");
}
}
输出:
B的static的n1被调用
C的普通代码块
C的无参构造器
B的普通代码块
B的无参构造器
6、我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④父类的构造器
⑤子类的普通代码块和普通方法属性初始化(优先级一样,按定义顺序执行)
⑥子类的构造器
例:分析下面代码的输出
public class Block05 {
public static void main(String[] args) {
new B02();//先加载父类A02 再加载子类B02 static静态方法是在类加载的时候就调用的
}
}
class A02{//父类
private static int n1 = getVal01();
static {
System.out.println("A02的一个静态代码块..");//(2)
}
{
System.out.println("A02的一个普通代码块..");//(5)
}
public int n3=getVal02();
public static int getVal01(){
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02(){
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//构造器
//隐藏了
//(1)super();
//(2)本类的普通代码块和普通属性的初始化
System.out.println("A02的构造器");//(7)
}
}
class B02 extends A02{
private static int n3 =getVal03();
static {
System.out.println("B02的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02的一个普通代码块..");//(9)
}
public static int getVal03(){
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04(){
System.out.println("getVal04");//(8)
return 10;
}
public B02(){
//隐藏了
//(1)super();
//(2)本类的普通代码块和普通属性的初始化
System.out.println("B02的构造器");//(10)
}
}
输出:
getVal01
A02的一个静态代码块..
getVal03
B02的一个静态代码块..
A02的一个普通代码块..
getVal02
A02的构造器
getVal04
B02的一个普通代码块..
B02的构造器
7、静态代码块只能调用静态成员(静态属性和静态方法)
9.4.4练习题
A01:分析下列代码
ic class Block06 {
public static void main(String[] args) {
System.out.println("total="+A01.total);//首先先加载A01,所以A01的static被调用
System.out.println("total="+A01.total);//因为static只会被调用一次,所以第二次现在不会调用,在第一次的时候total以及被赋值了
}
}
class A01{
public static int total;
static {
total = 100;//100返回上去(2)(3)
System.out.println("in static block!");//(1)
}
}
输出:
in static block!
total=100
total=100
第二条语句只输出total的值,因为static只会被调用一次,所以第二次现在不会调用,在第一次的时候total以及被赋值了
A02:分析下列代码的输出
public class Block07 {
public static void main(String[] args) {
Test test = new Test();//1、类加载
}
}
class Test{
Sample sam1 = new Sample("sam1成员初始化");//9、
static Sample sam = new Sample("静态成员sam初始化");//2、
static {
System.out.println("static块执行");//5、
if (sam == null){//6、在2那句sam已经被赋值了,所以sam不为null
System.out.println("sam is null");
}
}
Test(){//7、
//隐含了super(),父类是object类,所以没有输出,不考虑
//8、隐含了普通方法
System.out.println("Test默认构造函数被调用");//12
}
}
class Sample{
Sample(String s){//3、//10、
System.out.println(s);//4、//11、
}
Sample(){
System.out.println("Sample默认构造器被调用");
}
}
输出:
静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造函数被调用
9.3单例设计模式
9.3.1设计模式概念
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编码风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋谱、不同的棋局,我们用不同的棋谱,免去我们自己再去思考和摸索。
9.3.2 单例模式
1.单例模式,就是采取一定的方法保证再整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
2.单例模式有两种:①饿汉式 ②懒汉式
9.3.3 单例模式的应用实例
9.3.3.1 饿汉式
步骤:
① 将构造器私有化,防止用户直接new一个对象
② 在类的内部去创建对象
③ 向外暴露一个静态的公共方法
④ 代码实现
例:要求:创建一个GirlFriend类,并且要求只能有唯一一个对象
//要求:创建一个GirlFriend类,并且要求只能有唯一一个对象
public class SingleTon01 {
public static void main(String[] args) {
//用类去调用一个对象
System.out.println(GirlFriend.getInstance());
//用方法去获取对象,判断是否是同一个对象
GirlFriend gf1 = GirlFriend.getInstance();
GirlFriend gf2 = GirlFriend.getInstance();
System.out.println(gf1 == gf2);//true
}
}
class GirlFriend{
private String name;
// [
// [单例模式----饿汉式 ]
//步骤① 将构造器私有化,可有避免用户直接去new一个对象,那么就不是只有一个对象了
private GirlFriend(String name) {
this.name = name;
}
//步骤② 在类的内部创建一个对象,为了保证能够使用,其对象将其设置为static静态的
private static GirlFriend gf = new GirlFriend("江仔");
//步骤③ 提供一个公共的static方法,去返回 gf对象
public static GirlFriend getInstance(){
return gf;
}
//用toString方法去显示对象
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
输出:
GirlFriend{name='江仔'}
true
之所以叫做饿汉式,是因为一个对象没有被用,就已经随着类的加载被加载了,饿汉式可能造成创建了对象,到那时对象没有被使用
9.3.3.2 懒汉式
步骤:
1、构造器私有化
2、定义一个static静态属性
3、提供一个公共static方法,去返回一个Cat对象
4、只有当用户去使用getInstance()方法的时候,才会去返回一个cat对象, 后面继续调用的话,还是会返回上一次创建的cat对象,这样就保证了单例性
例:在程序的运行中,只能创建一个Cat对象
public class SingleTon02 {
public static void main(String[] args) {
//用类去调用其方法去创建对象
System.out.println(Cat.getInstance());
//用方法去创建对象
Cat cat1 = Cat.getInstance();
Cat cat2 = Cat.getInstance();
System.out.println(cat1 == cat2);
}
}
//在程序的运行中,只能创建一个Cat对象
//使用单例模式
class Cat{
private String name;
//步骤① 构造器私有化
private Cat(String name) {
this.name = name;
}
//步骤②定义一个static静态属性
private static Cat cat;//没有初始化的时候,默认是null,当然也可以赋值为null
//步骤③ 提供一个公共static方法,去返回一个Cat对象
//步骤④ 只有当用户去使用getInstance()方法的时候,才会去返回一个cat对象,
// 后面继续调用的话,还是会返回上一次创建的cat对象,这样就保证了单例性
public static Cat getInstance(){
if (cat == null){//如果还没有创建Cat对象就创建一个
cat = new Cat("江仔");//将其步骤2定义的属性去创建其对象
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式,主要就是用户要使用这个对象的时候才去创建这么一个对象去使用,不会造成资源浪费。
9.3.3.3 饿汉式、懒汉式区别
1、创建对象的时机不同:饿汉式是在类加载的时候就已经创建了对象实例,而懒汉式是在使用的时候才创建的
2、饿汉式不存在线程安全的问题,懒汉式存在线程问题
3、饿汉式存在浪费资源的可能性,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是在使用的时候才创建,就不会存在这个问题
4、在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式
Runtime源码如下:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
9.4 final关键字
9.4.1 final关键字概念
final可以修饰类、属性、方法和局部变量
应用场景:
1、在当一个类不希望被继承的时候,可以用final关键字修饰
2、当不希望类的某一个方法被子类重写或者覆盖的时候,可以用final关键字修饰
3、当不希望类的某个属性的值不被修改时,可以用final关键字修饰
4、当不希望某个局部变量被修改的时候,可以用final关键字修饰
9.4.2 final关键字细节
1、final修饰的属性又叫做常量,一般用 XX_XX_XX 命名,也就是命名时大写,且用下划线隔开
例: TAX_RATE = 10;
2、final修饰的属性在定义时,必须要赋初值,并且不能够再修改,赋值的位置以下之一:
① 定义时去赋值
②在构造器中去赋值
③在代码块中去赋值
3、如果final修饰的属性事静态的,则初始化的位置只能是在以下之一:
① 定义时
②在静态的代码块中
因为要遵循static关键字的语法使用规则,所以static的属性只能在static的方法中去使用赋值。
4、final类不能继承,但是可以实例化对象
5、如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但是可以被继承
5、一般来说,如果一个类已经时final类了,就没有必要去再把方法修饰 成final方法
(因为类已经被final修饰了,那就整个类都不会被诶继承了,那么其他类自然也就不能够去重写类方法了,所以方法再用final修饰一次,有一点多余了)
6、final不能去修饰构造器
7、final关键字和static往往搭配使用,效率更高,不会导致类的加载(static是在确保不用创建对象的情况下,只要类被加载的,就可以去调用方法,两者结合的话,就不会导致类加载)
public class Final03 {
public static void main(String[] args) {
System.out.println(AAA.num);
System.out.println(BBB.num1);
}
}
class AAA{
public static int num = 10;
static {//用来测试会不会被类加载
System.out.println("AAA的静态代码块被调用...");
}
}
class BBB{
public final static int num1 = 20;
static {//用来测试会不会被类加载
System.out.println("AAA的final 静态代码块被调用...");
}
}
输出:
AAA的静态代码块被调用...
10
20
8、包装类(Integer、Double、String等都是final类,所以是不能被继承的)
源码:
9.4.3 final关键字练习题
/A01 编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写,并且时用对象去调用的/
package com.xioawang.final_;
/*编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写*/
public class FinalText1 {
public static void main(String[] args) {
Circle circle = new Circle();
System.out.println(circle.are());
}
}
class Circle{
private double radius = 2;//半径先写定死,可以更改的
//1、定义属性时去赋初值,并且通过对象去调用
private final double PI ;//= 3.14;
//2、构造器中去赋初值,并通过对象去调用
public Circle() {
// this.PI = 3.14;
}
//3、代码块中赋值
{
PI = 3.14;
}
public double are(){
return radius*radius*PI;
}
}
输出:
12.56
/A02 编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写,并且时用类加载去调用/
public class FinalText2 {
public static void main(String[] args) {
System.out.println(Circle1.are());
}
}
class Circle1{
private static double radius = 2;
private final static double PI ;
static {
PI=3.14;
}
public static double are(){
return radius*radius*PI;
}
}
输出:
12.56
第十章
抽象类 、 接口 与内部类
10.1抽象类
10.1.1 抽象类引出
当一个父类的某一些方法需要声明,但是呢又不知道该如何去实现,一般都是子类去具体实现的,这时候就可以将其父类设置为抽象类。
例:有一个动物,有eat方法,但是具体怎么实现不知道
public class Abstract01 {
}
abstract class Animal{
private String name;
//以下代码说实话没有具体意义,只要求声明一下,所以直接将其设计为abstract抽象类
//abstract抽象类就是没有具体实现方法的方法
//没有实现方法也就是没有方法体
//需要注意:在一个类中有abstract抽象方法的时候,则该类要设计为abstract类
/*public void eat(){
System.out.println("这是动物吃东西的方法...");
}*/
public abstract void eat();
}
1、abstract抽象类就是没有具体实现方法的方法
2、需要注意:在一个类中有abstract抽象方法的时候,则该类要设计为abstract类
10.1.2 抽象类介绍
1、用abstract关键字来修饰一个类时,这个类就叫抽象类
语法:
访问修饰符 abstract 类名{
}
2、用abstract关键字来修饰一个方法时,这个方法就是抽象方法
语法:
访问修饰符 abstract 返回类型 方法名 (参数列表); //注意:是没有方法体的
3、抽象类更多在于设计,是设计者设计好后,让子类继承并实现抽象类()
4、抽象类,是考官比较爱问的知识点,在框架和设计模式中使用较多
10.1.3 抽象类细节
1、抽象类不能被实例化
2、abstract抽象类不一定要包含abstract抽象方法,也可以有实现的方法
3、类里面有abstract抽象方法那么该类一定是abstract抽象类
4、abstract抽象只能去修饰类和方法,不能修饰属性和其他
5、抽象类可以有任意成员(因为抽象类的本质始终还是一个类),比如有:非抽象的方法、构造器、静态属性等等
6、抽象方法不能有主体
7、如果一个类继承了抽象类,那么它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象类
8、抽象方法不能使用private、final 和 static 来修饰,因为这些关键字和重写是像制约的
10.1.4抽象类最佳实践----模板设计模式
例:
分别计算多个类中,完成不同任务所需要的时间,
AA类 计算1+…+1000000所花的时间
BB类 计算1*…500000的时间
思路:在两个类中分别通过 结束时间-开始时间 去计算不同类完成某工作的时间
public class AA {
public static void main(String[] args) {
long start = System.currentTimeMillis();//获取当前时间
long sum = 0;
for (long i = 0; i < 1000000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("AA 所花的时间 "+(end - start));
}
}
public class BB {
public static void main(String[] args) {
long start = System.currentTimeMillis();//获取当前时间
long sum = 0;
for (long i = 0; i < 500000; i++) {
sum *= i;
}
long end = System.currentTimeMillis();
System.out.println("BB 所花的时间 "+(end - start));
}
}
以上解法,可以看出方法比较繁琐,两个类中有共同的语句了,所以,我们可以通过将主要的计算方法单独包装成一个方法,然后再用的时候去调用即可,优化后代码:
public class AA {
public static void main(String[] args) {
long start = System.currentTimeMillis();//获取当前时间
work();
long end = System.currentTimeMillis();
System.out.println("AA 所花的时间 "+(end - start));
}
//将主要计算功能单独成一个方法,然后在main方法中去调用即可
public static void work(){
long sum = 0;
for (long i = 0; i < 1000000; i++) {
sum += i;
}
}
}
public class BB {
public static void main(String[] args) {
long start = System.currentTimeMillis();//获取当前时间
work();
long end = System.currentTimeMillis();
System.out.println("BB 所花的时间 "+(end - start));
}
//将主要计算功能单独成一个方法,然后在main方法中去调用即可
public static void work(){
long sum = 0;
for (long i = 0; i < 500000; i++) {
sum *= i;
}
}
}
以上方法明显比第一个简单一点,但是还是比较繁琐,要是当要计算的类比较多的时候,这样写也是很繁琐的,所以,通过以上代码我们可以看出来,不同类的基本结构都是一样的,都是先获取开始时间,然后运行功能方法,运行完后获取结束的时间,然后用结束时间去 - 开始时间 ,这样就可以获得类完成功能的时间,所以,我们可以将具体的功能方法封装成一个abstract抽象方法,子类具体去实现,然后将时间具体时间也给封装成一个方法。
优化后的代码
public class AA extends Template {
//将主要计算功能单独成一个方法,然后在main方法中去调用即可
public void work(){
long sum = 0;
for (long i = 0; i < 1000000; i++) {
sum += i;
}
}
}
public class BB extends Template{
//将主要计算功能单独成一个方法,然后在main方法中去调用即可
public void work(){
long sum = 0;
for (long i = 0; i < 500000; i++) {
sum *= i;
}
}
}
public abstract class Template {
public abstract void work();//定义一个抽象的方法,利用子类去重写
public void calculateTime(){//编写一个计算时间的方法去计算时间
long start = System.currentTimeMillis();//获取当前时间
work();0
long end = System.currentTimeMillis();//获取结束时间
System.out.println("执行时间:"+(end - start));
}
}
public class Abstract04 {
public static void main(String[] args) {
new AA().calculateTime();
new BB().calculateTime();
}
}
10.2 接口
10.2.1 接口的基本介绍
接口(interface)就是给一些没有实现的方法,封装在一起,到某一个类要使用的时候,再根据实际情况去把这些方法写出来。
语法:
interface 接口名{
//属性
//方法(1、抽象方法 2、默认实现方法(需要用default修饰) 3、静态方法)
}
当一个类要去使用接口的时候
语法:
class 类名 implement 接口名{
//自己的属性
//自己的方法
//注意:必须要实现接口的抽象方法
}
注意:
1、在 jdk7.0 前,接口里所有的方法都是抽象方法,也就是没有方法体的
2、在 jdk8.0 后,接口可以有静态方法、默认方法,也就是接口中可以有方法的具体实现
public interface Interface01 {//创建一个接口
//1、接口可以有自己的属性,但是必须要初始化赋值,不然会报错
public int sum = 10;
public String name="acd";
//2、可以有抽象方法,abstract关键字 可以不写,默认就是抽象方法
public void cry();
//public abstract void cry1();
//3、可以是默认实现方法,但是必须要default去修饰
default public void cry2(){
System.out.println("默认实现方法...");
}
//4、可以是静态方法
public static void cry3(){
System.out.println("静态方法...");
}
}
public class Interface01Text implements Interface01{
//用类去实现接口,因为接口中有抽象方法,所以必须要去实现所有抽象方法,不然会报错
@Override
public void cry() {
System.out.println("类实现接口cry1");
}
}
10.2.2接口的细节
1、接口不能被实例化
2、接口中,所有的方法都是 public,而且 public 已经是默认的了,所以也可以不写,接口中的抽象方法,也可以不用 abstract 去修饰
3、一个普通的类实现接口,就必须将该接口的所有方法都实现,主要是抽象方法必须实现,要是接口中有default 修饰的默认实现方法,或者static修饰的静态方法,则可以不用去实现(11.1中的代码可以深刻的体现)
如果需要实现的方法较多的时候,可以用快捷键(alt + enter)去一键补全
4、抽象类实现接口,可以不用实现接口的方法
5、一个类,可以实现多个接口,每一个接口的方法都必须要去实现,和上面细节3的要求一样
6、接口中的属性,只能是final的,修饰符是 public static final 去修饰的,并且必须要初始化
7、接口中属性的访问形式是: 接口名 . 属性名
8、接口不能继承其他的类,但是可以继承多个其他接口(接口与接口之间是 extends 继承,接口和类之间是 implement 实现)
注意:一个类要是继承了有父类的接口,那么这个类就必须要去实现这个接口本身以及所有父类的所有为实现的抽象方法
9、接口的修饰符,只能是 public 和 默认,这一点和类的修饰符是一样的
10.2.3接口练习
判断下面代码是否有语法错误,没有的话,输出什么?
public class InterfaceText01 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb.a);
System.out.println(Ainter.a);
System.out.println(BB.a);
}
}
interface Ainter{
int a = 20;
}
class BB implements Ainter{ }
解答:
package com.xioawang.interface_;
public class InterfaceText01 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb.a);//因为BB实现了Ainter这个接口,所以类的实例对象是可以去访问接口属性的,所以输出20
System.out.println(Ainter.a);//接口名去调用接口属性没问题,输出20
System.out.println(BB.a);//因为BB实现了Ainter这个接口,所以可以用类名.属性名去访问属性,所以输出20
}
}
interface Ainter{
int a = 20;
}
class BB implements Ainter{ }
输出:
20
20
20
10.2.4实现接口和继承的区别
1、接口的实现,是对Java单继承机制的补充
例:
现在有一只小猴子、一只老猴子、一只鸟、一条鱼;小猴子是老猴子的子类,从生下来就继承了老猴子会爬树的行为,但是呢,小猴子要是想要会飞,那么就只能去找鸟去学习飞翔,相当于就是小猴子去实现鸟飞翔的行为,小猴子要是想学会游泳,就去找鱼学习,然后去实现鱼游泳的行为
package com.xioawang.interface_;
public class InterfaceVsExtends {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("江仔");
littleMonkey.climing();
littleMonkey.flying();
littleMonkey.swimming();
}
}
class Monkey{
private String name;
public Monkey(String name) {
this.name = name;
}
public void climing(){
System.out.println(name+" 会爬树...");
}
public String getName() {
return name;
}
}
interface Bird{
void flying();
}
interface Fish{
void swimming();
}
class LittleMonkey extends Monkey implements Bird,Fish{//继承一个类后,就有了父类的功能
public LittleMonkey(String name) {
super(name);
}
@Override
public void flying() {
System.out.println(getName()+" 会飞翔...");
}
@Override
public void swimming() {
System.out.println(getName()+" 会游泳...");
}
}
结论:
1、当子类继承父类了后,子类就自动拥有了父类的功能
2、如果子类还想要拥有其他的功能,那么可以用实现接口的方式去扩展
3、实现接口,是对 Java 单继承机制的一种补充
2、接口和继承解决的不同问题
继承的价值:解决代码的复用性和可维护性
接口的价值:设计好各种规范(方法),让其它类去实现这些方法,也就是接口更加灵活
3、接口比继承更加灵活
接口比继承更加灵活,继承是 is - a 的关系,而接口是 like - a 的关系,通俗来说就是,猫是一个动物,但是同时可以通过接口去实现,像人一样的动作行为
4、接口在一定程度上实现代码解耦
在接口规范性 + 动态绑定机制 的基础上去实现代码的解耦
10.2.5 接口的多态性
1、接口的多态参数
也就是,接口的引用可以指向实现了接口类的对象实例;
语法:
接口类名 接口变量名 = new 实现接口的类名();
public class InterfacePloyParameter {
public static void main(String[] args) {
//接口体现多态
//接口变量cInter 可以指向 实现了 DInterface 的对象实例
DInterface cInter = new C();
cInter = new D();
//继承体现多态
//父类BBB的变量 bbb 可以指向 继承了BBB父类的所有子类的对象实例
BBB bbb = new CC();
bbb = new DD();
}
}
interface DInterface{}
class C implements DInterface{}
class D implements DInterface{}
class BBB{}
class CC extends BBB{}
class DD extends BBB{}
2、接口的多态数组
例:
在 Usb数组中,存放 Phone 和 Camera 对象,Phone类还有一个特有的方法call(),请遍历 Usb 数组,如果存放Phone对象,除了调用Usb 接口定义的方法外,还需要调用Phone的特有方法call()
public class InterfacePloyArr {
public static void main(String[] args) {
Usb [] usb = new Usb[3];//先定义一个usb数组去存放对象
usb[0] = new Phone("华为");
usb[1] = new Phone("苹果");
usb[2] = new Camera("索尼");
//可以存放多种类型的类对象,就已经说明了接口有多态数组了
for (int i = 0; i < usb.length; i++) {
System.out.print(usb[i].message());
if (usb[i] instanceof Phone) {//判断运行类型
System.out.println(" "+((Phone) usb[i]).call());
}
}
}
}
interface Usb{
String message();
}
class Phone implements Usb{
private String name;
public Phone(String name) {
this.name = name;
}
public String call(){
return getName()+"正在打电话ing...";
}
public String getName() {
return name;
}
@Override
public String message() {
return "我是:"+getName()+"手机";
}
}
class Camera implements Usb{
private String name;
public Camera(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String message() {
return "我是: "+getName()+"相机";
}
}
3、接口的多态传递
也就是两个接口之间存在继承关系,实现子接口的类相当于也实现了父接口
代码解释如下:
package com.xioawang.interface_;
/*演示接口的多态传递*/
public class InterfacePloyPass {
public static void main(String[] args) {
Finterface finterface = new E();//Finterface接口的变量,指向E类的实例对象,因为E类实现了Finterface接口
Einterface einterface = new E();//因为Finterface接口继承了Einterface接口,
// Einterface接口的变量可以指向E类的实例对象,就证明了,接口之间有传递性
Dinterface dinterface = new E();//FALSE,因为Finterface接口、Einterface接口与Dinterface接口之间没有继承关系,
// 所以E类的实例对象不能够实现Dinterface
}
}
interface Dinterface{}
interface Einterface{}
interface Finterface extends Einterface{}//接口与接口之间是继承关系,接口和类之间是实现关系
class E implements Finterface{ }
上面代码中要特别注意这句:
10.3 内部类
10.3.1 四种内部类
内部类基本介绍:
一个类里面又完整的嵌套了另一个类结构,被嵌套的类称为内部类,嵌套内部类的类叫做外部类,内部类是类的第五大成员
【类的五大成员:属性、方法、构造器、代码块、内部类】
内部类特点:
内部类可以直接访问类的私有属性,同时可以体现类与类之间的包含关系
内部类基本语法:
class Outer { //外部类
class inner{ //内部类
}
}
class Other{//其他类
}
快速入门案例:
/*类介绍*/
public class InnerClass01 {//外部其他类
public static void main(String[] args) {
}
}
class Outer{//外部类
private String name;//外部类的属性
public void cry(){ }//外部类的方法
public Outer(String name) {//外部类的构造器
this.name = name;
}
class Inner{//内部类
}
}
注:内部类一共有四种:
1、定义在外部类的局部位置上(比如在方法内)
①局部内部类
②匿名内部类(重点!!!!)
2、定义在外部类的成员位置上:
①成员内部类(没有static修饰)
②静态内部类(使用static修饰)
10.3.2 局部内部类
基本介绍:
局部内部类就是定义在外部类的局部位置上,比如在方法中(通常也是在方法中),并且有类名
说明:
1、可以直接访问外部类的所有成员方法,包括私有的成员方法
2、不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。
但是可以用 final 修饰,因为局部变量也可以使用final
**3、作用域:仅仅只能在定义它的方法或者代码块中**
4、局部内部类可以直接访问外部类的成员
5、外部类 访问 局部内部类 的成员,访问方式:先创建对象,再访问(注意:必须要在作用域内)
注:只能在局部内部类所在的外部类的方法中去创建对象,并且是在类创建完了后,才能实例化对象
6、外部其他类 ---不能访问----》局部内部类 (因为 局部内部类的地位是一个局部变量,作用域也只能在其所在的方法或者代码块中)
7、如果外部类的成员 和 局部内部类的成员 重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用: 外部类名.this.成员
/*局部内部类演示*/
public class JuBuInnerClass {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.hi();
System.out.println("main方法中的 Outer05.this 的hashcode值= "+outer05);
}
}
//6、如果外部类的成员 和 局部内部类的成员 重名时,默认遵循就近原则
class Outer05{
private int n1 = 1;
public void hi(){
class Inner05{
private int n1 = 2;
}
Inner05 inner05 = new Inner05();
//如果想访问外部类的成员,则可以使用: 外部类名.this.成员
System.out.println("n1 = "+n1+" 外部类的n1 = "+ Outer05.this.n1);
//7、Outer05.this 的本质就是外部类的对象,
// 即 是哪个实例化对象调用了n1,就是那个对象,这里是outer05对象
System.out.println("内部的 Outer05.this 的hashcode值= "+Outer05.this);
//如果这里输出的hashcode值和main方法中的一样,则证明 7 结论没错
}
}
输出:
n1 = 1 外部类的n1 = 1
内部的 Outer05.this 的hashcode值= com.xioawang.InnerClass.Outer05@1b6d3586
main方法中的 Outer05.this 的hashcode值= com.xioawang.InnerClass.Outer05@1b6d3586
注:<外部类名.this.成员> 中 <外部类名.this> 的本质是一个对象,即 是哪个实例化对象调用了含有局部内部类的方法,就是那个
10.3.3匿名内部类
10.3.3.1匿名内部类介绍
解析:
1、本质是一个类
2、内部类
3、该类没有名字(不能自己写,jdk底层会给分配一个:外部类名 $1)
4、匿名内部类同时也是一个对象(因为jdk底层再创建一个匿名内部类的时候,就会立即去实例化一个对象)
说明:
1、匿名内部类是定义在外部类的局部位置的,比如在方法中,或者代码块中
基本语法:
new 类 或 接口(参数列表){
类体
}
10.3.3.2匿名内部类使用案例
案例:基于接口的匿名内部类
package com.xioawang.InnerClass;
/*演示基于接口的匿名内部类使用*/
public class AnonymousInnerClass {
public static void main(String[] args) {
A a = new A();
a.classCry();
}
}
class A{//外部类
public void classCry(){//基于接口的匿名内部类
//1.需求:用接口Ia,并创建一个对象
//2.传统方法,写一个类,去实现接口Ia,再创建一个对象
//3.所要创建的对象,只使用一次,后面不再用,要是按照传统方法,就很浪费资源
//4.这时可以通过匿名内部类来简化开发代码
//5.tiger的编译类型:Ia接口
//6.tiger的运行类型:匿名内部类 名称:外部类的名称+$1,这里就是A$1
/*
底层代码:会分配一个 类名 :外部类的名称+$1
* class XXXX implement Ia{
* public void InterCry() {
System.out.println("老虎叫...");
}
* }
* */
//7.jdk底层再创建匿名内部类,就立即创建了一个匿名内部类的实例,
// 并且把地址返回给了编译类型的名称,这里就是tiger
//8.匿名内部类使用一次就不能再使用了
Ia tiger = new Ia(){//接口是不能直接new的,但是后面有了方法体以及用tiger去接收
@Override
public void InterCry() {
System.out.println("老虎叫...");
}
};
tiger.InterCry();
System.out.println("匿名内部类的名称:"+tiger.getClass());//获取某一个类id类名称
}
}
interface Ia{//接口
public void InterCry();
}
输出:
老虎叫...
匿名内部类的名称:class com.xioawang.InnerClass.A$1
总结:
相当于就是,一个对象只希望被用一次,但是呢,按照传统方法再去创建一个类去实现接口再实例化对象,就会觉得很代码繁琐,为了简化,就可以创建一个匿名内部类去实现接口,再代码的底层中,会有一个底层分配的类去实现这个接口,并且实例化一个对象,匿名内部类的方法体就是你所要实现的方法。同时,基于接口的时候,接口中任何一个方法,都必须要在匿名内部类中去实现。毕竟这个类要去实现这个接口。
案例:基于类的匿名内部类
/*演示基于类的匿名内部类使用*/
public class AnonymousInnerClass {
public static void main(String[] args) {
A a = new A();
a.classCry();
}
}
class A{//外部类
public void classCry(){
//1.创建一个对象,要是后面没用{ 方法体 },那么这就是一个普通的类,要是又{ }那就是一个匿名内部类
//2.b的编译类型:B类
//3.b的运行类型:A$1 这个匿名内部类,如果后面还有匿名内部类,$后面的数字一次类推就行
//4.底层代码中:匿名内部类
//5.匿名内部类所传入的参数是要传给构造器的
//6.同时也直接返回了匿名内部类的对象
/*
* class A$1 extends B {
* 方法体内就是你所要执行的语句这里相当于是
* @Override
public void test() {
System.out.println("匿名内部类 重写tset方法....");
}
* }
*/
B b = new B("Tom"){
//匿名内部类可以重写方法
@Override
public void test() {
System.out.println("匿名内部类 重写tset方法....");
}
};
b.test();
System.out.println("b对象的运行类型:"+b.getClass());//验证3的正确性
}
}
class B{
private String name;
public B(String name) {
System.out.println("所接收到的name:"+name);
}
public void test(){
}
}
输出:
所接收到的name:Tom
匿名内部类 重写tset方法....
b对象的运行类型:class com.xioawang.InnerClass.A$1
总结:
基于类的匿名内部类,需要i注意:有{ }的才是匿名内部类,没有{ }的不是匿名内部类,同时{ }的方法体是可以不需要去写或者是重写的,并且,再底层代码中,匿名内部类的实现是通过 extends 继承的,同时创建过后也会直接实例化传给所接收的对象,并且参数列表的值,会传递到构造器中去。
案例:基于抽象类的匿名内部类
/*演示基于抽象类的匿名内部类使用*/
public class AnonymousInnerClass {
public static void main(String[] args) {
A a = new A();
a.classCry();
}
}
class A{//外部类
public void classCry(){
C c = new C(){
@Override
void eat() {
System.out.println("抽象类的匿名内部类中的抽象方法必须要写");
}
};
c.eat();
}
}
abstract class C {//抽象类
abstract void eat();
}
输出:
抽象类的匿名内部类中的抽象方法必须要写
总结:
和基本的 基于类的匿名内部类是一样的,最大的区别就是,基于抽象类的时候,抽象类的所有抽象方法都要去实现。
10.3.3.3匿名内部类细节
1、从匿名内部类的语法中可以看出,它既是一个类的定义,同时也是一个对象,所以调用匿名内部类的方法也是很多种:
① 创建一个类对象,再用对象名去调用方法,这时的匿名内部类就是类的定义
②直接通过匿名内部类去调用方法,这时,匿名内部类就是一个对象
2、匿名内部类可以访问外部类的所有成员属性,包括私有
3、不能添加访问修饰符,因为它的地位就是一个局部变量
4、作用域:匿名内部类也只能在方法或者代码块中,并且只能使用一次
5、<外部其他类> - - -不能访问—<匿名内部类>(因为 匿名内部类的地位是一个局部变量)
6、如果外部类和内部类的成员属性同名的时候,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以用(外部类名 . this . 成员属性)
10.3.3.4匿名内部类最佳实践
1、当匿名内部类做为实参直接传递的时候
package com.xioawang.InnerClass;
/*将匿名内部类当作实参传递时,前提是这个类使用的次数很少或者一次*/
public class InnerClassExerise01 {
public static void main(String[] args) {
//传统方法:先创建一个类去实现接口,以及接口的方法,然后创建类对象去当作实参传递到方法中
BB bb = new BB();
fi(bb);
//使用匿名内部类去做实参
fi(new Ib() {//创建了一个Ib的接口匿名内部类
@Override
public void show() {
System.out.println("匿名内部类...");
}
});
}
//
public static void fi(Ib ib){//形参为接口的静态方法
ib.show();
}
}
interface Ib{//一个接口
void show();
}
class BB implements Ib{
@Override
public void show() {
System.out.println("传统方法...");
}
}
总结:
使用传统的方法去做的时候,比较繁琐,得先创建一个类,类去实现接口及方法,然后创建类对象,当作实参去传递方法;而匿名内部类的本质也是一个类和对象,所以可以通过直接用匿名内部类去当作实参传递,前提是,这个类所使用的次数很少。
============================
2、有一个铃声的接口Bell,里面有个ring方法;有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型;测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床…;再传入另一个匿名内部类(对象),打印:小伙伴上课…
package com.xioawang.InnerClass;
public class InnerClassExercise02 {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();//创建一个手机对象
cellphone.alarmclock(new Bell() {
//用匿名内部类去做实参去调用方法
//编译类型是Bell接口,运行类型是 InnerClassExercise02$1
@Override
public void ring() {
System.out.println("懒猪起床..");
}
});
cellphone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课...");
}
});
}
}
interface Bell{//铃声接口
void ring();//ring方法
}
class Cellphone{
public void alarmclock(Bell bell){//形参是一个接口类型
bell.ring();//返回接口的ring方法,动态绑定了
}
}
输出:
懒猪起床..
小伙伴上课...
分析下代码:
前提:接口、类这些准备工作准备好后
1、题目要求测试手机类,所以我们先创建一个手机对象
2、其次要求每一个实参传入就输出相应的铃声,说明这个铃声是只用一次的,所以用匿名内部类即可,就不用再去将手机类去实现接口,因为实现接口的话,一定要把接口的方法给实现了,那么可以说这个方法就已经具体了,虽然后面可以重写,但是代码还是会有冗余
3、手机类的方法所需要传入的实参是接口对象,那么可以使用匿名内部类去实现,再匿名内部类中去重写接口的 ring()方法,在这个地方,ring方法的编译类型是 接口 Bell ,但是运行类型不是手机类,而是 内部类所在的外部类的 外部类名 $ 1,所以,输出的不可能再是手机类里面的 ring()方法,而是外部类里面的匿名内部类的 ring()方法
10.3.4 成员内部类
基本介绍:
成员内部类是定义在外部类的成员位置的,并且没有 static 修饰
说明:
1、成员内部类可以访问外部类的所有成员,包括私有的(因为成员内部类的位置就是外部类的成员,所以我们要去调用实现它的话,可以在外部类中写一个方法,然后在方法中去创建成员内部类的对象,再通过这个对象去调用成员内部类的方法、成员)
2、可以添加任意的访问修饰符(public、private、protected、默认),因为它的地位就是外部类的一个成员
3、作用域:和外部类的其他成员一样,在整个类体中可以使用;比如在前面案例中,在外部类的成员方法中创建成员内部类对象,再去调用方法。
4、成员内部类 可以直接访问 外部类 的所有成员,包括私有的
5、外部类 访问 成员内部类(先创建对象,再去访问)
6、外部其他类 可以访问 成员内部类,两种访问方式(既是:先创建一个外部类的对象,然后在外部类对象的基础上,再去创建一个成员内部类的对象)
public class MenberInnerClass01 {
public static void main(String[] args) {
Outer06 outer06 = new Outer06();
//外部其他类访问成员内部类的方法
//1、outer06.new Inner06(); outer06是外部类的一个实例对象
// 相当于把 new Inner06() 当作是outer06的成员,用outer06对象去调用new Inner06()成员
Outer06.Inner06 inner06 = outer06.new Inner06();
inner06.say();
//2、在外部类中去写一个方法,然后返回 Inner06对象
Outer06.Inner06 inner061 = outer06.getInner06();//用一个内部类去接收outer06方法的返回对象
inner061.say();//再用inner061对象去调用方法
}
}
class Outer06{//外部类
class Inner06{//成员内部类
public void say(){//是可以直接访问外部类的成员属性的,包括私有的
}
}
public Inner06 getInner06(){//编写一个方法去返回一个成员内部类对象
return new Inner06();//new Inner06()相当于就是创建了一个内部类对象,然后返回
}
}
7、如果外部类和成员内部类的成员重名的时候,内部内访问的话,默认遵循就近原则。如果想访问外部类的成员,则使用 外部类名 . this. 成员名
public class MenberInnerClass01 {
public static void main(String[] args) {
Outer06 outer06 = new Outer06();
outer06.cry();
}
}
class Outer06{//外部类
private int n1 = 5;//外部类私有成员属性
class Inner06{//成员内部类
private int n1 = 10;
public void say(){//是可以直接访问外部类的成员属性的,包括私有的
System.out.println("n1 = "+n1);
System.out.println("外部类的n1 = " + Outer06.this.n1);
}
}
public void cry(){//通过写一个方法,在方法中去成员内部类的对象
// 通过对象去调用我们的成员内部类方法
Inner06 inner06 = new Inner06();
inner06.say();
}
}
输出:
n1 = 10
外部类的n1 = 5
10.3.5 静态内部类
基本介绍:
静态内部类是定义在外部类的成员位置上,并且有 static 修饰
说明:
1、可以直接访问外部类的所有静态成员,包括私有的,但是不能直接访问非今天非静态成员
2、可以添加任意的访问修饰符(public、protected、private、默认),因为它本来就是一个成员
3、作用域:同其他成员,是整个类体
4、静态内部类 - - - -访问 - - - > 外部类(比如说:静态属性),直接访问即可,注意只能是静态的
5、外部类 - - - 访问- - - >静态内部类 访问方式:先创建对象,再访问
6、外部其他类 - - -访问- - -静态内部类
package com.xioawang.InnerClass;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.omg.CORBA.PRIVATE_MEMBER;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer07 outer07 = new Outer07();
//6、外部其他类 - - -访问- - -静态内部类
//方式1:
//是直接用外部类名去调用成员的方法,但是必须要遵循访问权限(private啥的就不可以)
Outer07.Inner07 inner07 = new Outer07.Inner07();
inner07.cry();
//方式2 :
//编写一个方法去返回一个静态内部类的对象实例
Outer07.Inner07 inner071 = outer07.getInner071();
inner071.cry();
//方式3:
//编写一个静态方法去返回一个静态内部类的对象实例,这种不需要去创建一个外部类对象
Outer07.Inner07 inner072 = Outer07.getInner07();
inner072.cry();
}
}
class Outer07{
private int n1 = 10;//非静态成员属性
private static String name = "江仔";//静态成员属性
private static void hi(){ }//静态方法
static class Inner07{//静态内部类,也是一个成员
// 1、可以直接访问外部类的所有静态成员,包括私有的,但是不能直接访问非今天非静态成员
//2、可以添加任意的访问修饰符(public、protected、private、默认),因为它本来就是一个成员
//3、作用域:同其他成员,是整个类体**
//4、静态内部类 - - - -访问 - - - > 外部类(比如说:静态属性),直接访问即可,注意只能是静态的**
//5、外部类 - - - 访问- - - >静态内部类 访问方式:先创建对象,再访问**
private int sum = 1;
public void cry(){
System.out.println(name);
hi();
}
}
public static Inner07 getInner07(){
return new Inner07();
}
public Inner07 getInner071(){
return new Inner07();
}
public void ok(){
Inner07 inner07 = new Inner07();//创建一个静态内部类的对象
inner07.cry();//调用静态内部类的方法
}
}
输出:
江仔
江仔
江仔
7、如果外部类和静态内部类的成员重名时,静态内部类访问时,默认就近原则,要是要访问外部类成员,那么直接用:外部类名 . 成员,但是要确保成员属性是静态的 (这里不需要加 this 是因为本来就已经是static 修饰的静态成员了,加了this完全就没有意义)
package com.xioawang.InnerClass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer07.Inner07 inner07 = new Outer07.Inner07();
inner07.cry();
}
}
class Outer07{
private static int n1 = 10;//非静态成员属性
static class Inner07{//静态内部类,也是一个成员
private int n1 = 2;
public void cry(){
System.out.println("n1= "+n1+"外部类的n1= " + Outer07.n1);
}
}
}
输出:
n1= 2外部类的n1= 10
==============================
呀,到这里呢,我们的Java基础部分就全部学完了,这个过程呢,哈哈,怎么说呢,真的有点小乏味呀,知识点又多,后面要多多巩固呀,然后整装待发下个部分了~~~~
======================================
第十一章
枚举 和 注解
11.1 枚举
11.1.1枚举介绍
1、枚举对应的英文(enumeration),简称(enum)
2、枚举是一组常量的集合
3、可以理解成:枚举属于一种特殊的类,里面只包含一组有限的特定对象
11.1.2枚举的实现方式
1、自定义类实现枚举
//1、直接将构造器定义成私有的,因为是要防止对象被程序员去创建
//2、要去掉setXXX方法,防止被修改,因为枚举类的对象属性等成员是固定的,不能被修改
//3、在类的内部去创建所需要的固定对象,因为要保证外面类能用,所以用public去修饰
//4、用 final static 去修饰,为了确保,外部类能够直接使用static,已经防止属性被加载用final
//5、枚举对象的对象名通常使用全英文大写,因为常量的规范名
//6、枚举对象根据需要,可以有多个属性
案例:创建一个season(季节)类对象
package com.xiaowang.enum_;
/*演示自定义枚举类*/
public class Enumeration01 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}
class Season{
private String name;
private String desc;
//创建四个固定对象
public final static Season SPRING = new Season("春天","温暖");
public final static Season SUMMER = new Season("夏天","温暖");
public final static Season AUTUMN = new Season("秋天","温暖");
public final static Season WINTER = new Season("冬天","温暖");
//1、直接将构造器定义成私有的,因为是要防止对象被程序员去创建
//2、要去掉setXXX方法,防止被修改,因为枚举类的对象属性等成员是固定的,不能被修改
//3、在类的内部去创建所需要的固定对象,因为要保证外面类能用,所以用public去修饰
//4、用 final static 去修饰,为了确保,外部类能够直接使用static,已经防止属性被加载用final
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {//写一个toString方法去显示对象属性,更直观
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
输出:
Season{name='春天', desc='温暖'}
小结:自定义类实现枚举的特点
1、构造器私有化
2、本类内部创建一组对象(案例中是四个,也可以多个)
3、对外暴露对象(用 public final static 去修饰)
4、可以提供get()方法去获取信息,但是不能有set()方法去修改
2、enum关键字实现枚举
//1、直接将构造器定义成私有的,因为是要防止对象被程序员去创建
//2、要去掉setXXX方法,防止被修改,因为枚举类的对象属性等成员是固定的,不能被修改
//3、创建常量对象直接用 :常量对象名(实参列表)
//5、枚举对象的对象名通常使用全英文大写,因为常量的规范名
//6、枚举对象根据需要,可以有多个属性
//7、所创建的常量对象,要放在类的第一行
案例:创建一个season(季节)类对象
package com.xiaowang.enum_;
/**
* @Author 小王
* @DATE: 2022/3/11
*/
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}
enum Season2{
//演示enum关键字实现枚举类
//1、用关键字 enum 替换 class ,构造器、方法等不用改变
//2、创建对象的时候,可以直接用 对象名(实参列表)去替换自定义枚举类的创建方法,这里就是:
// SPRING("春天","温暖") 去替换 public final static Season SPRING = new Season("春天","温暖");
//3、如果要创建多个常量对象用 ,号 去隔开
//4、如果要使用enum关键字来实现枚举类,那么,创建的常量对象要写在最前面
SPRING("春天","温暖"),SUMMER("夏天","温暖"),AUTUMN("秋天","温暖"),WINTER("冬天","温暖");
private String name;
private String desc;
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {//写一个toString方法去显示对象属性,更直观
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
输出:
Season{name='春天', desc='温暖'}
小结:enum关键字实现枚举的特点
1、当外面使用enum 关键字开发一个枚举类时,默认会 继承 Enum类,而且是一个final类,可以用 javap 去反编译查看
2、传统的 public static final Season SPRING = new Season(“春天”, “温暖”); 简化成 SPRING(“春天”, “温暖”), 这里必须要知道,对象时调用哪个构造器,主要通过所传入的实参列表来确定。
=============
3、如果使用无参构造器去创建常量对象,则可以省略 (实参列表) 包括括号可以省略
4、当有多个枚举对象时,使用 , 间隔,最后有一个分号结尾
5、用enum关键字来实现的时候,枚举对象必须放在枚举类的行首
11.1.3枚举练习
A01:判断下面代码是否有错:
enum Gender{
BOU,GIRL;
}
答:没有错;
① Gender 类是用 enum 关键字去实现的 Gender 枚举类;
②BOY , GIRL调用的是枚举类的无参构造器,这个构造器是默认的,因为类中没有其他的构造器去覆盖默认的无参构造器
③要是类中有其他构造器,要想使之这语法没错,就得再显性的说明一下无参构造器
A02判断以下代码的输出:
public class Enumeration03 {
public static void main(String[] args) {
Gender boy = Gender.BOY;
Gender boy2 = Gender.BOY;
System.out.println(boy);
System.out.println(boy == boy2);
}
}
enum Gender{
BOY,GIRL;
}
输出:
BOY
true
输出部分代码分析:
System.out.println(boy);
/* 这里是要调用自己的toString方法,但是Gender类没有这个方法
所以就去调用父类Enum类的toString方法,结构如下:
public String toString(){
return name();
}
所以就输出对象名 BOY 呗*/
System.out.println(boy == boy2);
//因为枚举类是final static 去修饰的,那么不同被赋值的对象的实质还是同一个对象,所以返回True
11.1.4 enum常用方法说明
1、使用关键字 enum时,会隐式的继承Enum类,所以我们可以使用Enum类相关的方法
①、name()方法 {返回当前对象名(常量名),子类中不能重写}
②、ordinal()方法 {返回当前对象所在的位置,从0开始编号}
③、value()方法 {主要就是去存放所有的枚举对象,可以用for循环去查看}
④、valueOf()方法 {将字符串转换成枚举对象,要求字符串必须是枚举对象中已经有的常量名,否则会报错}
⑤、compareTo()方法 {比较两个枚举常量,比较的就是value()得到的那个编号}
package com.xiaowang.enum_;
/**
* @Author 小王
* @DATE: 2022/3/11
*/
public class EnumMethod {
public static void main(String[] args) {
Season2 autumn = Season2.AUTUMN;
//用Season2来演示
//1、name() 返回当前对象名(常量名),子类中不能重写
System.out.println(autumn.name());
//2、ordinal() 返回当前对象所在的位置,从0开始编号
System.out.println(autumn.ordinal());
//3、values方法,从javap反编译的类中可以看出来,是有这个方法的
// 主要就是去存放所有的枚举对象,可以用for循环去查看
Season2[] values = Season2.values();
System.out.println("=======普通for方法查看==========");
for (int i = 0; i < values.length ; i++) {
System.out.println(i);
}
System.out.println("=======增强for方法查看==========");
//增强for循环方法,是将常量对象去赋值给输出对象
//这里是:将values所存放的对象,依次去赋值给season2,然后输出,输出玩即退出循环
for (Season2 season2: values){
System.out.println(season2);
}
//4、valueOf:将字符串转换成枚举对象,要求字符串必须是枚举对象中已经有的常量名,否则会报错
//执行流程:
//1、根据你所输入的字符串去枚举对象中查找
//2、如果找到了,就返回,否则就报错
Season2 spring = Season2.valueOf("SPRING");
System.out.println("spring = "+spring);
//5、compareTo:比较两个枚举常量,比较的就是value()得到的那个编号
//就是把自己的 编号 和传入对象的编号进行比较
//这里就是 Season2.AUTUMN编号 和 Season2.SUMMER编号进行比较
//compareTo的关键实现代码是:
/*public final int compareTo(E o) {
下面就是:Season2.AUTUMN 编号(2) - Season2.SUMMER 编号(1),所以输出:1
return self.ordinal - other.ordinal;
}*/
System.out.println(Season2.AUTUMN.compareTo(Season2.SUMMER));
}
}
输出:
AUTUMN
2
=======普通for方法查看==========
0
1
2
3
=======增强for方法查看==========
Season{name='春天', desc='温暖'}
Season{name='夏天', desc='炎热'}
Season{name='秋天', desc='凉爽'}
Season{name='冬天', desc='寒冷'}
spring = Season{name='春天', desc='温暖'}
1
11.1.5 enum练习题
A01:
①声明Week枚举类,其中包含星期一至星期日的定义,MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY、SUNDAY;
②使用values 返回所有的枚举数组,并遍历,输出以下效果图
package com.xiaowang.enum_;
/**
* @Author 小王
* @DATE: 2022/3/11
*/
public class EnumText {
public static void main(String[] args) {
Week[] weeks = Week.values();
System.out.println("===所有星期的信息如下==");
for (int i = 0; i < weeks.length ; i++) {
System.out.println(weeks[i]);
}
}
}
enum Week{
MONDAY("星期一"),TUESDAY("星期二"),WEDNESDAY("星期三"),
THURSDAY("星期四"),FRIDAY("星期五"),SATURDAY("星期六"),SUNDAY("星期日");
private String days;
Week(String days) {
this.days = days;
}
@Override
public String toString() {
return days ;
}
}
输出:
===所有星期的信息如下==
星期一
星期二
星期三
星期四
星期五
星期六
星期日
11.1.6 enum实现接口
1、使用enum关键字后,就不能再继承其他类了,因为enum会隐式继承Enum。而且Java是单继承机制的
2、枚举类和普通类一样,可以实现接口,形式:
enum 类名 implements 接口1,接口2{ }
11.2注解
11.2.1 注解介绍
注解理解:
1、注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息
2、和注释一样,注解不影响程序逻辑,但是注解可以被编译或运行,相当于嵌再代码中的补充信息
3、在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的校色,例如用来配置应用程序的任何切面,代替Java EE旧版中所遗留的繁冗代码和XML配置。
基本的 Annotation 介绍:
在使用Annotation时要在其前面加上 @ 符号,并把该Annotation 当成一个修饰符使用,用于修饰它支持的程序元素
自定义注解:
修饰符 @interface 注解名{ }
注解使用:@注解名
- 方法上
- 属性上
- 变量上上
- 枚举上
- 注解上
注解的属性:
三个基本的Annotation:
① @Override:限定某个方法,是重写父类方法,该注解只能用于方法
② @Deprecated:用于表示某个程序元素(类、方法)已经过时
③ @SuppressWarnings:抑制编译器警告
11.2.2 Override注解
package com.xiaowang.annotat_;
/**
* @Author 小王
* @DATE: 2022/3/12
*/
public class Override_ {
public static void main(String[] args) {
}
}
class Father{
public void cry(){
System.out.println("你是猪??");
}
}
class Son extends Father{
@Override//表示重写了父类的cry方法,写了@Override就代表重写了父类的方法,编译器会去校验是否是重写
public void cry() {
System.out.println("你才是猪..");
}
@Override//编译器到这里的时候检查到父类并没有此方法,所以就会报错
public void hi(){}//false
}
注意:
写了@Override就代表重写了父类的方法,编译器会去校验是否是重写,如果发现并不是
重写,那么编译器就会报错
追进去@Override 看看具体是什么:
补充说明:
@interface 不是指这个是一个 interface 接口,而是一个注解类,是在jdk5.0过后才加入的
Override 使用说明:
1、@Override 表示指定重写父类的方法(可以从编译层面去验证),如果父类没有此方法,则会报错
2、如果不写 @Override 注解,但是子类中还是有父类的方法,那么仍然构成重写
3、@Override 只能修饰方法,不能修饰其他类、包、属性等
4、查看@Override 注解源码为:
说明只能修饰方法
5、@Target 是修饰注解的注解,称为元注解
11.2.3 Deprecated 注解
@Deprecated 用于表示某个程序元素(类、方法)已过时
package com.xiaowang.annotat_;
/**
* @Author 小王
* @DATE: 2022/3/14
*/
public class Deprecated_ {
public static void main(String[] args) {
A a = new A();
a.cry();//方法、属性、类有中划线就代表被@Deprecated修饰,表示已经过时了,
System.out.println(a.sum);
}
}
//1、@Deprecated修饰某个元素代表过时
//2、过时了,但是不代表不能被使用,只是不推荐再使用
@Deprecated
class A{
@Deprecated
public int sum = 10;
@Deprecated
public void cry(){
}
}
@Dprecated 说明:
1、用于表示某个程序元素(类、方法等)已过时
2、可以修饰:方法、类、包、字段、参数 等等
3、可以修饰以下:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE,
METHOD, PACKAGE, PARAMETER, TYPE})
4、@Deprecated 的作用可以做到新旧版本的兼容和过渡
11.2.4 SuppressWarnings 注解
@SuppressWarnings :抑制编译器警告(就是代码中黄色警告)
package com.xiaowang.annotat_;
import java.util.ArrayList;
/**
* @Author 小王
* @DATE: 2022/3/14
*/
//1、当不想看到这些警告的时候,可以使用 @SuppressWarnings 注解来抑制警告信息
//2、在{" "}中,可以写入你希望抑制(不显示)的警告信息
//3、@SuppressWarnings的使用范围,和你防止的位置相关的
// 例如:如果@SuppressWarnings只写在main方法,那么就只能抑制main方法中的,具体抑制看{"内容"}
// 在其他方法中就不能被抑制,如果要全部抑制,可以直接方法类上
@SuppressWarnings({" "})//具体见以下列表
public class SuppressWarnings_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("jack");
arrayList.add("jack");
int i;
}
}
可以存放的关键字及其用途:
SuppressWarnings源码:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
从SuppressWarnings 源码中可以看到:
1、可以放置的位置有:
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
2、该注解类有数组 String[] value(),说明可以设置一个数组,比如:
@SuppressWarnings({“rawtypres”,“unchecked”,“unused”})
11.2.5 JDK的元 Annotation(元注解)
元注解基本介绍:
JDK 的 元注解Annotation 用于修饰其他 Annotation
元注解:本身的作用不大,但是可以让人们在看源码的时候,知道是干嘛的
元注解的种类:
① Retention 指定注解的作用范围,三种:SOURCE、CLASS、RUNTIME
② Target 指定注解可以在哪些地方使用
③ Documented 指定该注解是否惠再 javadoc 中体现
④ Inherited 子类会继承父类注解
11.2.5.1 Retention注解
说明:
只能用于修饰一个 Annotation 定义,用于指定该 Annotation 可以保留多长时间,
@Retention 包含一个 RetentionPolicy 类型成员,只用 @Retention
时必须为该 value 成员变量指定值。
@Retention三种值:
11.2.5.2 Target 注解
说明:
用于修饰Annotation定义,用于指定被修饰的 Annotation 能用于哪些程序元素,@Target 也包含一个名为 value 的成员变量
例如:
11.2.5.3 Documented 注解
说明:
@Documented 用于 指定被该 元Annotation 修饰的 Annotation 类 将被 javadoc 工具提取成文档,也就是在生成文档的时候,在文档中可以看到该注解
注:
定义为Documented的注解,必须设置 Retention的值为 RUNTIME (主要是要被保留在文档中嘛)
11.2.5.4 Inherited 注解
说明:
被 @Inherited修饰的 Annotation 将具有继承性,也就是,如果某一个类使用了被 @ Inherited修饰的 Annotation ,则这个类的子类也将自动具有该注解
11.3练习题
A01:
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/14
*/
public class Homework01 {
public static void main(String[] args) {
System.out.println(Frock.getNextNum());
System.out.println(Frock.getNextNum());
Frock frock = new Frock();
Frock frock1 = new Frock();
Frock frock2 = new Frock();
System.out.println(frock.getSerialNumber());
System.out.println(frock1.getSerialNumber());
System.out.println(frock2.getSerialNumber());
}
}
class Frock{
private static int currentNum = 100000;
private int serialNumber;
public Frock() {
serialNumber = getNextNum()
;
}
public static int getNextNum(){
currentNum += 100;
return currentNum;
}
public int getSerialNumber() {
return serialNumber;
}
}
输出:
100100
100200
100300
100400
100500
A02:
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/14
*/
public class Homework02 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.shout();
Dog dog = new Dog();
dog.shout();
}
}
abstract class Animal{
public abstract void shout();
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("猫会喵喵叫");
}
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("狗会汪汪叫");
}
}
输出:
猫会喵喵叫
狗会汪汪叫
注意:抽象类的方法是没有方法体的;既然一个类有抽象方法,那么类也是抽象类;一个类继承一个抽象类,那么抽象类的抽象方法必须要实现
A03:
package com.xiaowang.homework_;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
/**
* @Author 小王
* @DATE: 2022/3/14
*/
public class homework03 {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
cellphone.testWork(new Computer() {//匿名内部类作参数传递
//编译类型:Computer 这个接口。运行类型就是 匿名内部类
@Override
public double work(double n1, double n2) {
return n1 + n2;//计算方法是可以修改的
}
},5,2);
}
}
class Cellphone{
//当我们调用testwork方法时,直接传入一个实现了Computer接口的匿名内部类即可
public void testWork(Computer computer,double n1,double n2){
double result = computer.work(n1,n2) ;
System.out.println("运算结果="+result);
}
}
interface Computer{
//题目没有具体要求方法,那么我们可以自己去写
double work(double n1,double n2);
}
输出:
运算结果=7.0
A04:
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/15
*/
public class Homework04 {
public static void main(String[] args) {
A a = new A();
a.cry();
}
}
class A{
private final String NAME = "外部类江仔";
public void cry(){
class B{//局部内部类
private final String NAME = "内部类江仔 ";
public void show(){
System.out.println("NAME = "+NAME);
//同名时,访问外部类的成员用:外部类名.this.成员名
System.out.println("NAME = "+A.this.NAME);
}
}
B b = new B();//调用局部内部类必须在内部类所在方法中的外部类上去创建对象然后再使用
b.show();
}
}
输出:
NAME = 内部类江仔
NAME = 外部类江仔
A05: 题:
1.有一个交通具接口类 Vehicles ,有 work 接口
2.有 Horse 类和 Boat 类分别实现 VehicLes
3.创建交通工具工厂类,有两个方法分别获得交通工具 Horse 和 Boat
4。有 Person 类,有 name 和 VehicLes 属性,在构造器中为两个属性赋值
5.实例化 Person 对象“唐僧”,要求一般情况下用 Horse 作为交通工具,遇到大河时用 Boat 作为交通工具
6.增加一个情况,如果唐僧过火焰山,使用飞机=>程序扩展性
package com.xiaowang.homework_;
import org.omg.CORBA.PUBLIC_MEMBER;
import sun.security.mscapi.CPublicKey;
/**
* @Author 小王
* @DATE: 2022/3/15
*/
public class Homework05 {
public static void main(String[] args) {
Person tang = new Person("唐僧", new Horse());
tang.passRiver();
tang.passRoad();
tang.passMountain();
}
}
class Person{
private String name;
private Vehicles vehicles ;
//构造器
public Person(String name,Vehicles vehicles){
this.name = name;
this.vehicles=vehicles;
}
//创建两个交通工具的方法,因为不同情况下有不同的过河方式
//因为再实例化对象的时候,已经设置了方式,所以我们得去判断一下
public void passRiver(){//当过河的时候
//防止始终使用的是传入的交通工具
// if (vehicles instanceof Boat) 改成 if (!(vehicles instanceof Boat))
//(1).vehicles = null :vehicles instanceof Boat ==>false
//(2).vehicles = 骑马 :vehicles instanceof Boat ==>false
//(3).vehicles = 乘船 :vehicles instanceof Boat ==>true
if (!(vehicles instanceof Boat)) {//当设置的交通工具是乘船的时候
this.vehicles = ToolFactory.getVehicles1();//返回一个船对象去赋值给 vehicles
}
vehicles.work();//此时的vehicles已经是 Boat 对象了,所以调用 Boat的work方法
}
public void passRoad(){//走正常情况下时
if (!(vehicles instanceof Horse)) {
this.vehicles = ToolFactory.getVehicles();
}
vehicles.work();//动态绑定
}
public void passMountain(){//过火焰山时
if (!(vehicles instanceof Plant)) {
this.vehicles = ToolFactory.getVehicles2();
}
vehicles.work();//动态绑定
}
}
class ToolFactory{//方便不用创建对象就是用方法,所以直接将其方法设置为静态的
//整个过程中,如果马骑的始终是同一匹,而在过河的时候,是不同的船情况:
public static Horse horse = new Horse();//通过饿汉式直接将马定
private ToolFactory(){ }//私有化构造器,防止程序员去new一个马对象
public static Horse getVehicles(){
// return new Horse();//直接返回交通工具的对象
return horse;//同一匹马,
}
public static Boat getVehicles1(){
return new Boat();
}
public static Plant getVehicles2(){
return new Plant();
}
}
interface Vehicles{
void work();
}
class Horse implements Vehicles{
@Override
public void work() {
System.out.println("骑马...");
}
}
class Boat implements Vehicles{
@Override
public void work() {
System.out.println("过河,所以要乘船...");
}
}
class Plant implements Vehicles{
@Override
public void work() {
System.out.println("过火焰山,所以坐飞机...");
}
}
输出:
过河,所以要乘船...
骑马...
过火焰山,所以坐飞机...
A06:题:
内部类练习
有一个 Car 类,有属性 temperature (温度),车内有 Air (空调)类,有吹风的功能 flow , Air 会监视车内的温度,如果温度超过40度则吹冷气。如果温度低于0度则吹暖气,如果在这之间则关掉空调。实例化具有不同温度的 Car 对象,调用空调的 flow 方法,测试空调吹的风是否正确
解法一:匿名局部内部类实现:
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/15
*/
public class Homework06 {
public static void main(String[] args) {
new Car(5).monitor();
new Car(-2).monitor();
new Car(55).monitor();
}
}
/*内部类练习
有一个 Car 类,有属性 temperature (温度),车内有 Air (空调)类,有吹风的功能 flow ,
Air 会监视车内的温度,如果温度超过40度则吹冷气。如果温度低于0度则吹暖气,如果在这之间则关掉空调。
实例化具有不同温度的 Car 对象,调用空调的 flow 方法,测试空调吹的风是正确
*/
class Car{
private double temperature;
public Car(double temperature){
this.temperature=temperature;
}
public void monitor(){
class Air{
public void flow(){
if (temperature<0){
System.out.println("温度小于0度,吹暖风");
}else if (temperature>40){
System.out.println("温度高于40度,吹冷风");
}else {
System.out.println("温度处于0-40之间,关闭空调");
}
}
}
Air air = new Air();
air.flow();
}
}
输出:
温度处于0-40之间,关闭空调
温度小于0度,吹暖风
温度高于40度,吹冷风
解法2:成员内部类
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/15
*/
public class Homework06 {
public static void main(String[] args) {
new Car(5).cry();
new Car(-2).cry();
new Car(55).cry();
}
}
/*内部类练习(局部内部类)
有一个 Car 类,有属性 temperature (温度),车内有 Air (空调)类,有吹风的功能 flow ,
Air 会监视车内的温度,如果温度超过40度则吹冷气。如果温度低于0度则吹暖气,如果在这之间则关掉空调。
实例化具有不同温度的 Car 对象,调用空调的 flow 方法,测试空调吹的风是正确
*/
class Car{
private double temperature;
//构造器
public Car(double temperature) {
this.temperature = temperature;
}
class Air{
public void flow(){
if (temperature<=0){
System.out.println("温度小于0度,吹暖风");
}else if (temperature>=40){
System.out.println("温度高于40度,吹冷风");
}else {
System.out.println("温度处于0-40之间,关闭空调");
}
}
}
public void cry(){
Air air = new Air();
air.flow();
}
}
输出:
温度处于0-40之间,关闭空调
温度小于0度,吹暖风
温度高于40度,吹冷风
A07:
package com.xiaowang.homework_;
import org.omg.CORBA.PRIVATE_MEMBER;
/**
* @Author 小王
* @DATE: 2022/3/15
*/
public class Homework07 {
public static void main(String[] args) {
Color color = Color.RED;
color.show();
//switch方法中,switch()中写枚举对象
//case 后面 写 常量值
switch (color){
case RED:
System.out.println("匹配到:红色");
break;
case BLUE:
System.out.println("匹配到:蓝色");
break;
case BLACK:
System.out.println("匹配到:黑色");
break;
case GREEN:
System.out.println("匹配到:绿色");
break;
case YELLOW:
System.out.println("匹配到:黄色");
break;
default:
System.out.println("没有匹配到");
}
}
}
enum Color implements IColor{
RED(255,0,0),BLUE(0,0,255),
YELLOW(255,255,0),BLACK(0,0,0),
GREEN(0,255,0);
private int redValue;
private int greenValue;
private int blueValue;
Color(int redValue, int greenValue, int blueValue) {
this.redValue = redValue;
this.greenValue = greenValue;
this.blueValue = blueValue;
}
@Override
public void show() {
System.out.println("三个属性值:\t"+"redValue = "+redValue+" greenValue = "
+greenValue+" blueValue"+blueValue);
}
}
interface IColor{
void show();
}
输出:
三个属性值: redValue = 255 greenValue = 0 blueValue0
匹配到:红色
========================
第十二章
异常 Exception
12.1 异常引入
分析下面代码,看看输出什么
解析:
package com.xiaowang.exception;
/**
* @Author 小王
* @DATE: 2022/3/20
*/
public class Eception01 {
public static void main(String[] args) {
int sum1 = 10;
int sum2 = 0;
//1.当程序执行到 sum1/sum2 的时候。因为 sum2 = 0,所以就会抛出一个异常ArithmeticException(Arithmetic算术)
//2.当异常抛出后,程序就不会往下面运行了,直接退出了,这样导致整个系统的健壮性就不好
//3.为了使代码还能运行,可以用异常处理机制来解决,使其还能够继续运行
//4.当一个程序员觉得某一段代码会有异常或者是问题的时候,可以使用 try-catch 异常处理机制来解决,从而保证健壮性
//5. 选中代码块,按 ctrl + alt + t ,然后选择 try - catch
//6.这样就算有异常,还是可以继续执行
//int result = sum1/sum2;
int result = 0;
try {
result = sum1/sum2;
} catch (Exception e) {
e.printStackTrace();//这里是将错误赋值给e对象,然后输出e对象,这里代码是可以修改的
System.out.println("错误原因:"+e.getMessage());//这是输出异常,同时可以输出你想要的
}
System.out.println("结果:" +result);
}
}
输出:
java.lang.ArithmeticException: / by zero
at com.xiaowang.exception.Eception01.main(Eception01.java:20)
错误原因:/ by zero
结果:0
快捷键: 选中觉得有异常的代码块,然后按 ctrl + alt +t ,然后选择try - catch
12.2 异常概念
1、基本概念
Java语言中,将程序执行中发生的不正常情况称为 ”异常“ (开发过程中的语法错误和逻辑错误不是异常)
11.3 异常体系图 !!
Throwable异常体系图
总结
编译异常:
①是出现在 java源文件中的
②编译器能够检查出来的,并且是必须要去做处理的异常,不然程序就编译不通过,就不能被运行
运行异常:
①是出现在字节码文件转向,内存中类加载运行中的;
②编译器检查不出来的异常,一般是编程时的逻辑异常,时程序员应该要去避免的异常
③对于运行时异常可以不做处理,因为不会影响编译,而且很普遍,要是每一个都去用try-catch处理,那么整个程序的可读性和运行效率就会产生很大影响
体系图制作步骤:
步骤1:先输入 Throwable 然后按 ctrl + b 去查看源代码, 然后选中 Throwable ,右键,然后选中如下,就饿可以看见体系图
步骤2:
步骤3:
12.4 常见的运行时异常
12.4.1 NullPointerException
空指针异常:
当应用程序视图在需要对象的地方使用null时,抛出该异常,体系图:
案例:
public class NullPointerException_ {
public static void main(String[] args) {
String name = null;
System.out.println(name.length());//因为name的长度是空的,所以输出的时候,就会报错
}
}
输出:
Exception in thread "main" java.lang.NullPointerException
at com.xiaowang.exception.NullPointerException_.main(NullPointerException_.java:10)
12.4.2 ArithmeticException
算术异常
出现异常的运算条件的时候,就会抛出算术异常,体系图:
案例:
public class NullPointerException_ {
public static void main(String[] args) {
int num1 =10;
int num2 = 0;
System.out.println(num1/num2);//因为num2是分母,所以不能为0,所以要报算术异常
}
}
输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.xiaowang.exception.NullPointerException_.main(NullPointerException_.java:16)
12.4.3 ArrayIndexOutOfBoundsException
数组下标越界异常:
用非法索引访问数组时抛出的异常,如果索引为负或者大于等于数组长度大小,则该索引就为非法的索引,体系图
案例:
public class NullPointerException_ {
public static void main(String[] args) {
// ArrayIndexOutOfBoundsException 异常
int [] arr1 = {1,2};
for (int i = 0; i <=arr1.length ; i++) {
//因为这个数组的长度为2,但是arr1.length前面加了=,所以i是要到3的,数组实际上没有那么多,所以就会抛出数组下标越界异常
System.out.println(arr1[i]);
}
}
}
输出:
1
2
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
at com.xiaowang.exception.NullPointerException_.main(NullPointerException_.java:22)
Process finished with exit code 1
12.4.4 ClassCastExcepton
类型转换异常:
当试图将对象强制转换为不是实例的子类时,就会抛出该异常,体系图:
案例:
public class NullPointerException_ {
public static void main(String[] args) {
A a = new B();//向上转型,a的编译类型 A,运行类型 B
B b = (B) a;//向下转型,将a强制转换成B本子类对象,没问题
C c = (C) a;//代码的意思是,将a强制转换成C本子类的对象,
// 但是呢,此时的a编译类型已经是B了,B类与C类之间并没有任何关系,所以强制转会抛出异常
}
}
class A{ }
class B extends A{ }
class C extends A{ }
输出:
Exception in thread "main" java.lang.ClassCastException: com.xiaowang.exception.B cannot be cast to com.xiaowang.exception.C
at com.xiaowang.exception.NullPointerException_.main(NullPointerException_.java:28)
Process finished with exit code 1
NumberFormatException
数字格式不正确异常:
当应用程序试图将字符串转换成一种数值类型,单该字符串不能转换为适应格式时,就会抛出该异常, 使用异常我们可以确保输入的是满足条件的数字
体系图:
案例:
public class NullPointerException_ {
public static void main(String[] args) {
String name2 = "小王";
String name1 = "123456";
//将String转换成int类型
int num = Integer.parseInt(name1);
System.out.println(num);//这是正确的,可以成功输出
int num2 = Integer.parseInt(name2);
System.out.println(num2);//这里的String转换成int不能有相应的格式去接收,所以就会报错
}
}
输出:
123456
Exception in thread "main" java.lang.NumberFormatException: For input string: "小王"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.xiaowang.exception.NullPointerException_.main(NullPointerException_.java:36)
12.5 编译异常
基本介绍:
编译异常是在编译期间,被jvm机检查出来有问题的地方,这种是必须要去处理的异常,否则代码不能通过编译,就不能运行
11.6 异常练习题
12.7 异常处理
12.7.1 基本介绍
异常发生时,对异常作出的处理方式
12.7.2 异常处理方式
12.7.2.1 try - catch - finally
基本介绍:
程序员在代码中捕获发生的异常,自行处理
处理机制图:
处理异常说明:
1、Java提供try和catch块来处理异常,try用于包含可能出错的代码,catch块用于处理try块中发生的异常,可以根据需求,在程序中出现多个try…catch块
2、基本语法:
try{
//可疑 的代码
//将出现异常的代码块生成对应的异常对象,然后传给catch块
}catch(异常对象){
//对异常的处理
}finally{
//finally是可有可无的
}
注意细节:
1、如果发生异常了,则异常后面的代码不会执行,将直接进入catch块去做出处理
public class TryCatchDetail {
public static void main(String[] args) {
//快捷键 CTRL+ ALT + T
//1、如果发生异常了,则异常后面的代码不会执行,将直接进入catch块去做出处理
try {//觉得会发生异常,所以用try-catch语句包起来
String str = "江仔";
int a = Integer.parseInt(str);//纯字的字符串不能转换为int数字,所以会报错
System.out.println("数字="+a);//如果没有错,那么这段话肯定会输出,有错就会被跳过
} catch (NumberFormatException e) {
System.out.println("异常信息= "+e.getMessage());
}
System.out.println("程序继续执行....");
}
}
输出:
异常信息= For input string: "江仔"
程序继续执行....
2、如果异常没有发生,那么try代码块就会顺序执行,不会进入catch块中
public class TryCatchDetail {
public static void main(String[] args) {
//快捷键 CTRL+ ALT + T
//2、如果异常没有发生,那么try代码块就会顺序执行,不会进入catch块中
try {//觉得会发生异常,所以用try-catch语句包起来
String str = "123";
int a = Integer.parseInt(str);//123字符串可以被转换格式为int
System.out.println("数字="+a);//所以没有错,所以这句话就会被执行
} catch (NumberFormatException e) {//因为try块中的语句没错,所以catch语句不会被执行
System.out.println("异常信息= "+e.getMessage());
}
System.out.println("程序继续执行....");
}
}
输出:
数字=123
程序继续执行....
3、如果不管是否发生异常,都希望某一段代码能够被执行(比如:释放资源,关闭文件、关闭连接),那么将所要执行的代码放入finally块中
public class TryCatchDetail {
public static void main(String[] args) {
//快捷键 CTRL+ ALT + T
//1、如果发生异常了,则异常后面的代码不会执行,将直接进入catch块去做出处理
//2、如果异常没有发生,那么try代码块就会顺序执行,不会进入catch块中
//3、如果不管是否发生异常,都希望某一段代码能够被执行(比如:释放资源,关闭文件、关闭连接),那么将所要执行的代码放入finally块中
try {//觉得会发生异常,所以用try-catch语句包起来
String str = "江仔";
int a = Integer.parseInt(str);//纯字的字符串不能转换为int数字,所以会报错
System.out.println("数字="+a);//如果没有错,那么这段话肯定会输出,有错就会被跳过
} catch (NumberFormatException e) {
System.out.println("异常信息= "+e.getMessage());
}finally {//无论有没有异常都要发生
System.out.println("有异常时的finally,不管发生什么都要执行的代码块放在finally中..");
}
try {//觉得会发生异常,所以用try-catch语句包起来
String str1 = "123";
int a = Integer.parseInt(str1);//123字符串可以被转换格式为int
System.out.println("数字="+a);//所以没有错,所以这句话就会被执行
} catch (NumberFormatException e) {//因为try块中的语句没错,所以catch语句不会被执行
System.out.println("异常信息= "+e.getMessage());
}finally {
System.out.println("无异常时的finally,不管发生什么都要执行的代码块放在finally中..");
}
System.out.println("程序继续执行....");
}
}
输出:
异常信息= For input string: "江仔"
有异常时的finally,不管发生什么都要执行的代码块放在finally中..
数字=123
无异常时的finally,不管发生什么都要执行的代码块放在finally中..
程序继续执行....
4、一个try块可以有多个catch块,捕获不同异常的时候进行不同的业务处理,要求是:父类的异常在后,子类异常在前,比如(Exception 在后,NullPointException 在前),如果发生异常,只会匹配一个catch
public class TryCatchDetail02 {
public static void main(String[] args) {
//既然觉得有异常,那就去捕获,CTRL+ALT+T,选择try- catch
//1.如果try语句块有多个异常
//2.一个try块可以有多个catch块去捕获异常,做出相应的异常处理
//3.但是,父类的catch块要写在子类catch块的后面
try {
Person person = new Person();
person = null;
person.getName();//肯定会报错空指针异常,NullPointException
int n1 = 10;
int n2 = 0;
int result = n1 / n2;//因为n2为0,所以肯定会报错ArithmeticException
} catch (NullPointerException e) {//专门用来捕获空指针异常的catch块
System.out.println("NullPointerException异常信息:" + e.getMessage());
} catch (ArithmeticException e) {//专门用来捕获算术异常的catch块
System.out.println("ArithmeticException异常信息:" + e.getMessage());
} catch (Exception e) {//这里同时相当于捕获了以上两个异常,因为它们都是Exception的子类
System.out.println("异常信息:" + e.getMessage());
//没有上面两个catch块,肯定只输出NullPointException异常,因为它在ArithmeticException上面,
// 有NullPointException异常了后直接进入catch块,下面ArithmeticException不会被执行
}finally {
}
}
}
class Person {
private String name = "江仔";
public String getName() {
return name;
}
}
输出:
NullPointerException异常信息:null
分析:
①虽然我们有两个异常,三个catch块,但是最后,异常只会去匹配一个catch块,具体匹配的一般是出现在前面的异常
②之所以父类要写在子类的后面,是因为父类本就可以包含所有的异常块,如果写在前面,那子类的catch块就没有任何意义了,已经被捕获了,所以就会报错吖
5、可以直接使用 try - finally 来配合使用,这种就相当于,不去捕获异常,因此程序也会被崩掉,通常应用在:执行一段代码,不管有没有异常,都必须要执行的某个业务
public class TryCatchDetail02 {
public static void main(String[] args) {
//既然觉得有异常,那就去捕获,CTRL+ALT+T,选择try- catch
try {
int n1 = 10;
int n2 = 0;
int result = n1 / n2;//因为n2为0,所以肯定会报错ArithmeticException
}finally {
System.out.println("不管发不发生,处不处理异常,我都要执行的操作");
}
System.out.println("程序继续...");//前面异常没有被catch处理,所以程序直接崩掉了,不会被执行
}
}
输出:
不管发不发生,处不处理异常,我都要执行的操作
Exception in thread "main" java.lang.NullPointerException
at com.xiaowang.tryCatch.TryCatchDetail02.main(TryCatchDetail02.java:18)
try - catch - finally 执行顺序:
① 如果没有发生异常,则执行try块中的所有语句,不执行catch块的语句,如果后面又finally块,则最后也要执行finally中的语句
② 如果出现异常,则try块中异常发生后,try块中剩下的语句不会被执行,将直接执行catch块中的语句,如果又finally,最后还需要执行finally中的语句
12.7.2.2 throws
基本介绍:
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
处理机制图:
Throws异常说明:
1、如果一个方法(中的语句执行时),可能产生某种异常,但是并不能确定如何让处理这种异常,则此方法应显示地声明抛出异常。表明该方法将不对这些异常做处理,而由该方法的调用者负责处理
2、在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以时方法中产生的异常类型,也可以是它的父类
package com.xiaowang.throws_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Throws01 {
public static void main(String[] args) {
}
public void f2() throws Exception{
//创建一个文件流对象,可能会产生异常
//1.鼠标移到红色下划线处可以看到是FileNotFoundException,这是一个编译异常,必须要做出处理
//2.可以使用try - catch - finally去处理
//3.可以使用throws,抛出异常,让调用f2方法的调用者(一般也是一个方法)去处理异常
//4.throws后面的异常类型可以是方法中产生的异常类型,也可以是其父类,当然子类和父类选择一个即可
FileInputStream file = new FileInputStream("d://aa.txt");
}
//5.throws后面也可以是一个 异常列表,也就是可以抛出多个异常,当然也可以用他们的父类 Exception
public void f3() throws FileNotFoundException,NullPointerException {
FileInputStream file = new FileInputStream("d://aa.txt");
}
}
Throws异常处理的注意事项:
1、对于编译异常,程序中必须要进行处理,比如 try - catch 或者 throws
2、对于运行时异常,程序中如果没有哦做出处理,默认就是throws的方式处理,最终由JVM机去暴力处理
3、子类重写父类方法的时候,对于异常抛出的规定:子类所抛出的异常必须和父类的异常一致,或者是父类异常的子类,可以通过Diagrams查看异常父子关系
4、在throws过程中,如果由try - catch ,就相当于已经处理异常,就可以不必去使用throws(二选一)
总结:
①两种异常处理的方法,选择一种就可以,
②如果每一个方法都不处理,最终交给JVM机来处理的话,那么JVM机就会输出此异常,然后直接退出程序
③如果没有异常处理机制,那么默认是throws处理机制
12.7.2.3 自定义异常
基本介绍:
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己去设计异常类,用于描述该错误信息
自定义异常步骤:
1、定义类:自定义异常类名(程序员自己写)继承Exception或者RuntimeEception
2、如果继承Exception,就属于编译异常
3、如果继承RuntimeException,就属于运行异常(一般来说,继承RuntimeException)
应用实例:
当我们接收Person对象年龄时,要求范围在18-120之间,否则抛出一个自定义异常(要求继承RuntimeException),并给出提示信息
/*当我们接收Person对象年龄时,要求范围在18-120之间,
否则抛出一个自定义异常(要求继承RuntimeException),并给出提示信息*/
public class CustomException {
public static void main(String[] args) {
int age = 121;
if (!(age >=18 && age<=120)){//throw手动的去扔出一个异常,后面是通过构造器实例化一个异常对象去判断
throw new AgeException("年龄需要在18-120之间");
}
System.out.println("年龄范围正确");
}
}
/*思路:
1.定义AgeException类去继承RuntimeException
2.给异常类创建一个构造器
3.在调用的时候通过设置构造器去判断*/
/*1.一般情况下自定义异常都是制作成运行时异常,好处就是可以使用默认处理机制*/
class AgeException extends RuntimeException{//定义一个异常类,继承RuntimeException
public AgeException(String message){//构造器
super(message);
}
}
12.7.2.4 throw和throws区别
throws:
意义:异常处理的一种方式,位置:放在声明方法处,后面跟的是异常类型
throw:
意义:手动生成异常对象的关键字,位置:方法体中,后面跟的是异常对象
throw案例:
分析下列代码的输出
package com.xiaowang.throw_;
/**
* @Author 小王
* @DATE: 2022/3/22
*/
public class ThrowException {
public static void main(String[] args) {
try {
ReturnExceptionDemo.methodA();//调用A方法,接收到返回的异常,所以传给下面e对象
} catch (Exception e) {
System.out.println(e.getMessage());//4.输出异常信息“制造异常”
}
ReturnExceptionDemo.methodB();//调用B方法
}
}
class ReturnExceptionDemo{
static void methodA(){
try {
System.out.println("进入A方法");//1.正常输出
throw new RuntimeException("制造异常");//创建一个异常,但是因为有finally,所以不会立即返回
//3.当finally块执行完毕后再返回,
} finally {
System.out.println("调用A方法的finally方法");//2.finally块必须要输出
}
}
static void methodB(){
try {
System.out.println("进入B方法");//4.没有错误,但是还是得再finally后面返回,返回“进入B方法”
return;//
} finally {
System.out.println("调用B方法的finally方法");//5.输出finally
}
}
}
输出:
进入A方法
调用A方法的finally方法
制造异常
进入B方法
调用B方法的finally方法
12.8 练习题
12.8.1 try - catch 练习题
A01:分析下列代码
package com.xiaowang.tryCatch;
import com.xiaowang.exception.NullPointerException_;
public class Exercise01 {
public static int method() {
try {
String[] names = new String[3];//字符串长度为3的数组
//数组对象没有实例化,所以,每个数组对象都是null
if (names[1].equals("tong")) {//因为数组对象是null,所以为出现NullPointerException
System.out.println(names[1]);
} else {
names[3] = "jack";
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {//那么就会执行这句catch语句块
return 3;//3就会被返回
} finally {//但是呢,有finally语句块,所以 4 是必须要被返回的
return 4;
}
}
public static void main(String[] args) {
System.out.println(method());//所以最后被返回来的肯定是finally块的 4 ,如果没有finally块,肯定就输出3
}
}
输出:
4
A02:分析下列代码:
public class Exercise02 {
public static int method() {
int i = 1;
try {
i++;//++在后,先用再加 i =1+1 = 2
String[] names = new String[3];//字符串长度为3的数组
//数组对象没有实例化,所以,每个数组对象都是null
if (names[1].equals("tong")) {//因为数组对象是null,所以为出现NullPointerException
System.out.println(names[1]);
} else {
names[3] = "jack";
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {//那么就会执行这句catch语句块
return ++i;//++在前,先加再用,此时 i = 2+1 = 3
} finally {//但是呢,有finally语句块,是必须要被返回的
return ++i;//必须要返回的,i = 3+1 = 4
}
}
public static void main(String[] args) {
System.out.println(method());//所以最后被返回来的肯定是finally块的 4 ,如果没有finally块,肯定就输出3
}
}
输出:
4
A03:分析下列代码
public class Exercise03 {
public static int method() {
int i = 1;
try {
i++;//++在后,先用再加 i =1+1 = 2
String[] names = new String[3];//字符串长度为3的数组
//数组对象没有实例化,所以,每个数组对象都是null
if (names[1].equals("tong")) {//因为数组对象是null,所以为出现NullPointerException
System.out.println(names[1]);
} else {
names[3] = "jack";
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {//那么就会执行这句catch语句块
return ++i;//++在前,先加再用,此时 i = 2+1 = 3,因为下面的finally没有返回语句,
// 所以此时i=3就被返回回去给了main方法中,但是不是立即返回,此时会出现临时变量temp =3
} finally {//但是呢,有finally语句块,是必须要执行的
++i;// 相当于就是 临时变量temp+1 = 4
System.out.println("i = "+ i);//finally是必须要执行的,所以这句话也是必须要输出的
}//只有当finally执行过后,才会返回catch块的值,然后结束catch块的异常处理
}
public static void main(String[] args) {
System.out.println(method());//此时接收到要输出3,但是呢,
// 只有当finally执行过后,才会返回catch块的值,// 然后结束catch块的异常处理
}
}
输出:
i = 4
3
特别注意:当finally中没有返回值的时候,前面catch块中的返回值不能立即返回,
会先赋值给一个临时变量,等finally结束后再返回
A04:
要求用户输入一个整数,如果用户输入的不是一个整数,就反复提示他输入整数,直到输入正确为止
package com.xiaowang.tryCatch;
import java.util.Scanner;
//如果一个用户输入的不是一个整数,就反复提示他输入整数,直到输入正确为止
public class exercise04 {
public static void main(String[] args) {
//思路
// 1、创建Scanner对象,定义一个变量来存放接收的值
//2、使用无限循环去接收输入(while)
//3、然后将输入的值去转换成int
//4、如果转换的时候发生错误,就会抛出异常,那么说明所输入的不是一个可以转成int的内容
//5、如果没有抛出异常,那么就break,退出循环
Scanner scanner = new Scanner(System.in);//创建Scanner对象
int num = 0;//定义一个变量来存放接收的值
while (true){
System.out.println("请输入一个整数");
try {
num = Integer.parseInt(scanner.next());//这里是将输入的转换成int类型,scanner.next()输入的内容,不管格式
break;//要是可以被转换成int,那么说明输入的正确,就跳出循环
} catch (NumberFormatException e) {//要是输入的不可以转换成int,就会抛出异常
System.out.println("你输入的不是一个整数");//因为无限循环,所以异常抛出后,继续去执行while里面的语句
}
}
System.out.println("输入的值是:"+num);
}
}
输出:
请输入一个整数
小王
你输入的不是一个整数
请输入一个整数
sdfasf
你输入的不是一个整数
请输入一个整数
123
输入的值是:123
12.8.2 总练习题
package com.xiaowang.homework_;
/**
* @Author 小王
* @DATE: 2022/3/22
*/
public class Homework01 {
public static void main(String[] args){//因为这里传入的是字符串的数组,所以需要将传入的参数进行int格式化
/*int n1,n2;*/
try {
//2.缺少命令行参数异常,就是确保输入的字符串数组的长度是正确的,
if (args.length !=2){//因为要输入两个参数,所以不能多也不能少
throw new ArrayIndexOutOfBoundsException("参数个数不正确");//手动抛出异常
}
//1.在这里数据类型转换的时候,可能会发生异常
//这一步验证要写在判断数组长度后面,不然的话,就已经先有了n1,n2两个数据,对数组长度的判断有影响
int n1=Integer.parseInt(args[0]);
int n2=Integer.parseInt(args[1]);
//3.确保分母不为0
double result = cal(n1,n2);
//4.如果没有异常,那么就输出值
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("参数输入格式不正确,必须是整数");
} catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());//输出异常信息
}
}
//编写cal方法,static修饰,可以直接被调用
public static double cal(int n1,int n2){
return n1/n2;
}
}
注:
这个题是直接利用了main方法的数组来进行验证的,相当于要去配置参数信息,方法如下:
A02:判断下列代码的输出:
public class Homework02 {
public static void fun(){
try {
throw new RuntimeException();//抛出一个运算类型的异常,在finally执行后返回
} finally {
System.out.println("B");//1.执行finally,输出B
}
}
public static void main(String[] args) {
try {
fun();//调用fun方法,发现调用的fun方法有异常,所以直接进入catch去处理
System.out.println("A");//不会被执行
} catch (Exception e) {
System.out.println("C");//2.输出catch块处理方法
}
System.out.println("D");//3.输出D
}
}
输出:
B
C
D
A03:说出下列代码是否会发生异常,如果会,是哪种异常,如果不会,则打印结果是什么:?注:和A01一样去配置验证
public class Homework03 {
public static void main(String[] args) {
if (args[4].equals("john")){
//如果args数组没有值,那么就会ArrayIndexOutOfBoundsException
//也可能会发生NullPointException异常
System.out.println("AA");
}else {
System.out.println("BB");
}
Object o = args[2];//没问题
Integer i = (Integer) o;
//o是字符串类型,不能强制转换成Integer类型,会报错ClassCastException
}
}
A04:
分析下列代码的输出:
public class Homework04 {
public static void main(String[] args) {
try {
showExce();//调用showExce方法,因为接收到了一个异常对象,所以进入catch块
System.out.println("A");
} catch (Exception e) {
System.out.println("B");//1.输出B
} finally {
System.out.println("C");//2.输出C
}
System.out.println("D");//3.输出D
}
public static void showExce() throws Exception{
throw new Exception();//直接抛出一个异常对象
}
}
输出:
B
C
D
第十三章
常用类
13.1 包装类
13.1.1、包装类(wrapper)的分类:
①针对八种基本数据类型相应的引用类型
②有了类的特点,就可以调用类中的方法
查看每一个的类的父类,从中就可以知道哪些类可以被调用:
13.1.2 包装类和基本数据的转换
1、在jdk5之前用的是手动的装箱和拆箱的方式,装箱就是:基本类型 -> 包装类型,反之就是拆箱
2、在jadk5(包含jdk5)以后,就是用自动的装箱和拆箱方式了但是底层源码还是调用手动的 valueOf() 方法
我们通过 int 和 Integer 的转换为案例来演示:
public class WrapperType {
public static void main(String[] args) {
int n1 = 50;
//jdk5前 手动装箱:
Integer integer1 = new Integer(n1);//相当于就是创建一个Integer类对象,然后传入int型的变量参数
/*
* Integer中有一个成员变量 value 是int类型 这个变量用来存放Integer代表的值
* 这个变量使用final修饰的 意味着Integer的值 是不能改变的
* 所以Integer的对象创建就是在给value赋值
*/
Integer integer2 = Integer.valueOf(n1);//使用valueOf()方法去装箱
//jdk5前 手动拆箱:
int n2 = integer2.intValue();//使用intValue()方法去拆箱,如果拆箱别的,就是 类对象名.数据类型Value
//jdk5后 自动装箱
Integer integer3 = n1;//其实底层调用的还是手动装箱的valueOf()方法
//jdk5后 自动拆箱
int n3 = integer3;//其实底层调用的还是手动的 intValue()方法,
}
}
对于其他的类型,都可以去自己类比尝试去转换试试,注意,byte是字节哦,也就是说是一个字节数组对象哈
注意:
1、只要有基本数据类型,不管其他的是什么类型,都是判断值是否相等
2、包装类型通过装箱、拆箱得到的数值,需要判断输入的 i 的值是否在 最小值(low:-128)、最大值(high:127)之间,如果在那么就是传入数组()返回,如果是通过new来的,那必然是不同对象
3、如果两边都是包装类型,并且要比较他们的值的时候,建议使用equals方法。通常使用确定的值对象去调用equals,equals里传不确定的对象值,不然需要判断传来的值是不是为null
4、
5、整数的数据类型里面都有缓存数组,范围都是-128~~127
6、小数的数据类型没有缓存数组,装箱的时候,直接new对象
测试:
A01:判断下列代码是否正确
Double d = 100d;
Float f = 1.5;
//课堂测试A01:在main中
Double d = 100d;//true,因为这里自动的调用了手动装箱中的Double.valueOf(100d)方法,换句话就是100d已经是一个对象了
Float f = 1.5;//false,因为1.5是一个基本数据类型,并没有被转换成类对象,所以会报错,如果是 1.5f 则就是对的
A02:判断下列代码输出是否一致:
//课堂测试A02:在main中
Object obj1 = true ? new Integer(1) : new Double(2.0);
//这里是一个三元运算符,因为Integer和Double都是Object的子类,
// 但是呢,Integer在三元运算符中的前一位判断中,那么最后对象是建立的Integer对象,注意提高优先级
System.out.println(obj1);//输出Integer对象:1.0,不是 1,原因在下面注意地方
Object obj2;//定义一个Object类
if (true){//如果正确,那么创建Integer对象
obj2 = new Integer(1);
}else {//如果错误,那么创建Double对象
obj2 = new Double(2.0);
}
System.out.println(obj2);//Integer对象:1,不会提高优先级
注意:在做三元运算符的时候,要把整个三元运算符看作一个整体, 如果两个的精度不一致,会提高精度,无论输出是谁,但是精度必须是两者中最高的,
精度:double > float > long > int >char
13.1.3 包装类和String数据的转换
以 Integer 和 String 作为案例:
public class WrapperString {
public static void main(String[] args) {
//1、包装类(Integer -> String)
Integer i = 10;
//方法一:
String str1 = i +" ";
//这里至少基于i的数字去加上一个空格去组成“1“,然后去赋值给一个str1对象,对于i的数据类型是没有改变的
//方法二:
String s = i.toString();//因为包装类基本上都有toString方法,所以可以直接调用
//方法三:
String s1 = String.valueOf(i);//使用valueOf方法
//2、String -> 包装类(Integer)
String str2 = "123";//注意是必须能够转换的
//方法一:
Integer integer1 = Integer.parseInt(str2);//使用了自动装箱
//方法二:
Integer integer2 = Integer.valueOf(str2);//使用valueOf方法
//方法三:
Integer integer = new Integer(str2);//直接new一个Integer对象,通过构造器去转换
}
}
13.1.4 Integer和Character类的常用方法
1、Integer:
- compareTo() 比较 小于返回-1 等于返回0 大于返回1
- main(a,b)返回a和b中的最小值
- max(a,b)返回a和b中的最大值
- parseInt( s )字符串转换成int
- parseInt( s ,x)字符串转为指定x进制的int
- toBinaryString(i)将数字转换成二进制的字符串
- toHexString( i )将数字转换成十六进制的字符串
- 同Octal String()
2、String:
可以通过以下方法去查看不同类的方法,这些方法都是可以直接 lei类型 . 方法名用的,要用啥就去查就行
步骤一:
步骤二:
方法图:
13.1.5 Integer 经典面试题
阅读以下代码,看看输出什么
public class IntegerExercise01 {
public static void main(String[] args) {
method();
}
public static void method(){
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false,两个对象,肯定不同,所有false
Integer m = 1;//装箱,使用了 Integer.valueOf(1)
Integer n = 1;//装箱,使用了 Integer.valueOf(1) 1在-128~127之间,所以没问题
System.out.println(m == n);//ture,所以相同的,不能直接自己去判断,要进入底层源码去查看是否一致
/*查看Integer.valueOf( )方法的底层源码:
//这是源码方法上面提示中的代码,显示了i的取值范围
This method will always cache values in the range -128 to 127,
public static Integer valueOf(int i) {//功能方法
if (i >= Integer.IntegerCache.low && i <= Integer.IntegerCache.high)
//判断 i 的值是否在 最小值(low:-128)、最大值(high:127)之间,如果在那么就是传入数组返回
return Integer.IntegerCache.cache[i + (-Integer.IntegerCache.low)];
return new Integer(i);
//如果不在最小值(low:-128)、最大值(high:127)之间,那么就是返回一个Integer对象
}*/
Integer x = 128;//128 不在最小值(low:-128)、最大值(high:127)之间,所以是返回的对象
Integer y = 128;//128 不在最小值(low:-128)、最大值(high:127)之间,所以是返回的对象
System.out.println(x == y);//false,因为是两个对象,所以不相等,
Integer i1 = 127;
int i2 = 127;//基本数据类型
// 注意:只要有基本数据类型,不管其他的是什么类型,都是判断值是否相等
System.out.println(i1 == i2);
}
输出:
false
true
false
true
13.2 String类 !!!
13.2.1String类的理解和创建对象
1、String对象用于保存字符串,也就是一组字符序列
实现接口: 继承Object类
2、字符串常量对象是用双引号括起的字符序列,例如 ”123“,”小王“
3、字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)都是占两个字节
4、String类常用的构造器:
所有构造器可以在diagram图里点击这个可查看:
5、String类实现了Serializble接口,说明String可以串行化,也就是可以在网络中传输
6、String类实现了Comparable接口,说明String对象可以比较大小
7、String 是 final 类,是不能被其他类继承的
8、String 有属性:private final char value[ ];用于存放字符串内容,相当于一个字符串数组,注意:因为 value 是 final 类型的,所以不能修改(是不能修改value的地址,可以修改地址里面的值
13.2.2 String对象的创建方法
方式一:直接赋值
String s = ” hsp “;
分析:
先在常量池中去查看是否有 ”hsp“ 的数据空间,如果有 s 直接指向常量池中数据的地址,如果没有,就在常量池中去创建一个数据空间,然后再指向,总之就是:直接赋值的 s 最终指向是指向常量池的
方式二:调用构造器
String s2 = new String( ” hsp “ )
分析:
先在堆中创建空间,空间里面有维护 final修饰的 value属性,value属性指向常量池中的数据空间,如果没有数据空间,那么就创建一个,如果有,就通过value去指向常量池中的数据空间地址,然后 S2 对象再去指向value属性的堆地址,
两种方法的内存分布图:
13.2.3 String练习题
A01:分析下列代码的输出:
public class String02 {
public static void main(String[] args) {
String a ="123";
String b ="123";
System.out.println(a.equals(b));//TRUE,equals是判断d,b数组里面的字符串是否相等
System.out.println(a == b);//TRUE,
/*过程:
1、因为直接赋值,所以 a 对象先去找常量池中有没有”123“,
2、显然一开始a没有找到”123“,那就再常量池中去创建了一个”123“的数据空间,设地址:0x11
3、常量池中有了”123“后,a就直接去指向”123“的地址(0x11)了,所以a的地址:0x11
4、然后创建一个b字符串,b也去常量池中找是否有”123“数据空间
5、显然找到了,因为之前a创建了,所以b就直接去指向”123“的数据空间,地址:0x11*/
}
}
输出:
true
true
A02:
分析下列代码的输出:
public class StringExercise03 {
public static void main(String[] args) {
String a = "123";// a指向常量池的”123“
String b = new String("123");// b指向堆中对象
System.out.println(a.equals(b));//true,比较数值
System.out.println(a == b);//false,比较对象,两者的对象地址所指向不同
System.out.println(a == b.intern());//true,
// intern()方法:返回字符串对象的规范表示,详细解释见下面解析:
System.out.println(b == b.intern());//false,b是一个对象,b.intern是直接指向常量池
}
}
输出:
true
false
true
false
解析:
intern()方法API的解释:当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自常量池的字符串。 否则,此String对象将添加到常量池中,并返回对此String对象的引用。
通俗理解:只要String对象调用了 intern ( ) 方法,那么这个对象就是指向常量池的
A03:
分析下列代码的输出:
public class StringExercise04 {
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "123";
Person person2 = new Person();
person2.name = "123";
System.out.println(person1.name.equals(person2.name));
System.out.println(person1.name == person2.name);
System.out.println(person1.name == "123");
}
}
class Person{
public static String name;
}
输出:
true
true
true
简易内存分布图:
13.2.4 字符串特性
1、 String是一个final类,代表不可变的字符序列
2、字符串是不可变的,一个字符串对象一旦被分配,其内容是不可以变的,要是修改字符串的值,那就重新建一个对象去指向就行
案例1:
分析下面代码,创建了多少个对象(由内存布局图可以看出来,一共创建了两个对象)
案例2:
分析下面代码,创建了多少个对象
案例3:
小结:c = a + b,底层是,先创建个StringBuilder sb = new StringBuilder()对象,然后 sb对象去调用sb.append(a)方法,再去调用sb.append(b)方法,append( )方法是在原来的字符串基础上去追加,sb是在堆中
重要规则:String c = “a” + “b”;该对象是常量相加,是指向常量池,而, String c = a + b;是对象相加,指向堆
案例4:
分下下列代码输出:
public class StringExercise06 {
public static void main(String[] args) {
String s1 = "hello";//s1指向池中 "hello"
String s2 = "abc";//s2指向池中 "abc"
String s3 = "helloabc";//s3指向池中"hellosbc"
String s4 = (s1 + s2).intern();//s4指向 是池中"hellozbc"
System.out.println(s3 == s4);//true 因为都指向池中
System.out.println(s3.equals(s4));//true 因为字符串内容相等
}
}
输出:
true
true
A04:
分析下列代码的输出
public class Test1 {
String str = new String("hello");
final char[] ch = {'j', 'a', 'v', 'a'};
public void change(String str, char ch[]) {
str = "java";
ch[0] = 'h';
}
public static void main (String[] args){
Test1 ex = new Test1();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.println(ex.ch);
}
}
输出:
hello and hava
内存分布图:
整体分析:
①:main方法中创建一个ex对象,所以进行类加载,ex中的属性就有str (属于String,所以 str 中还有 final 修饰的 value 属性)和 ch [ ] 数组,内容为 java
②:ex调用change方法,那就开辟一个新栈,方法中由两个参数 str (参数中的str和对象的str一样,指向对象堆中的 value属性,但是呢,方法中又去将str 赋值为 java ,因为value是final 修饰的,所以方法中的str就不再指向堆中的value 了,就在指向常量池中创建的java )和 ch(参数中的ch 和对象属性ch 一样,都指向 一个数组引用,方法中去修改ch[0]d 值,那自然对象堆中的ch[0]就被修改了)
③ 方法change结束后,change栈就消了,然后继续下面的代码,因为堆中的str没有被修改 ,而堆中的ch数组内容被修改了,所以输出就是: hello and hava
13.2.5 String常见方法.
案例:
public class StringMethod {
public static void main(String[] args) {
String s1 = "jiangzai";
String s2 = " * xiaowang ";
//1、equals 区分大小写,判断内容是否相等
System.out.println(s1.equals("JiangZai"));//false
//2、equalsIgnoreCase 不区分大小写,判断内容是否相等
System.out.println(s1.equalsIgnoreCase("JiangZai"));//true
//3、length 获取字符的个数,字符串的长度
System.out.println(s1.length());//8
//4、indexOf 获取字符或者局部字符串在整个字符串中,索引从0开始,第一次出现的索引,没有就返回-1
System.out.println(s1.indexOf("ex"));//获取字符串ex首次出现在s1中的索引,没有,所以-1
System.out.println(s1.indexOf('a'));//获取字a首次出现在s1中的索引,2
//5、lastIndexOf 获取字符或者局部字符串在整个字符串中,索引从0开始,最后一次出现的索引,没有就返回-1
System.out.println(s1.lastIndexOf('a'));//获取字a最后一次出现在s1中的索引,6
//6、substring(i,j) 获取指定索引(i,j)之间的字符串,索引从0开始,也就是获取i-(j-1)之间,注意是之间,所以不包括j
System.out.println(s1.substring(1,4));//获取s1:jiangzai中,索引为1-(4-1)之间的字符串 ian
//7、trim 去掉字符串最前面和最后面的空格
System.out.println(s2.trim());//要是不使用 肯定输出: * xiaowang ,但是trim所以输出:* xiaowang
//8、charAt 获取某索引处的字符,注意不能用 Str[index]数组这种方式
System.out.println(s1.charAt(1));//只能输出单个字符 i
}
}
输出:
false
true
8
-1
2
6
ian
* xiaowang
i
public class StringMethod1 {
public static void main(String[] args) {
String s1 = "xiaowang";
String s2 = "XIAOWANG";
//1.toUpperCase 将字符串转换成大写
System.out.println(s1.toUpperCase(Locale.ROOT));//XIAOWANG
//2.toLowerCase 将字符串转换成小写
System.out.println(s2.toLowerCase(Locale.ROOT));//xiaowang
//3.concat 在字符串后面拼接字符串,可以多次拼接
System.out.println(s1.concat(" sds").concat("tnt"));//xiaowang sdstnt
//4.replace 替换字符串中的字符(前面是久字符,后面是新字符),这里是区分大小写的
//注意:在这里,s1.replace 是返回替换后的对象,
// 相当于有一个对象去接收了s1.replace的结果,但是对于s1本身是没有影响的
System.out.println(s1.replace('a','A'));//xiAowAng
System.out.println(s1);//s1.replace对于s1没有任何影响 输出:xiaowang
//7.toCharArray 转换成字符数组
char [] cha = s1.toCharArray();//既然要转换成字符数组,那就要用一个字符数组来接收
System.out.println("=====转化成字符数组后=======");
for (int i = 0; i < cha.length ; i++) {
System.out.print(cha[i]+"\t");//不换行输出,用制表位去分割
}
//8.format 格式化字符串,
}
}
输出:
XIAOWANG
xiaowang
xiaowang sdstnt
xiAowAng
xiaowang
=====转化成字符数组后=======
x i a o w a n g
split的解析:
public class StringMethodSplit2 {
public static void main(String[] args) {
//5.split 分割字符串,用普通字符分割时
String poem = "鹅鹅鹅,曲项向天歌,白毛浮绿水,红草拨清波";
System.out.println("=========分割前的古诗========");
System.out.println(poem);
String [] split = poem.split(",");//()中间的是字符串中有的字符
//因为一开始是一长串字符串,然后分割后就变成了很多串,所以要用数组去接收被分割的串
//因为是字符串数组,所以要用for循环去输出
System.out.println("=========分割后的古诗========");
for (int i = 0; i < split.length ; i++) {
System.out.println(split[i]);
}
//5.split 分割字符串,有特殊字符时,用转义符 \\ 修饰
String s2 = "C:\\aa\\bb\\cc";
System.out.println("=========分割前的文件路径========");
System.out.println(s2);
//String [] split2 =s2.split("\\");//在字符串中以 \\ 去分割时,,会抛出错误
String [] split2 = s2.split("\\\\");//需要使用转移符 \\ 去修饰特殊字符
System.out.println("=========分割后的文件路径========");
for (int i = 0; i <split2.length ; i++) {
System.out.println(split2[i]);
}
}
}
输出:
=========分割前的古诗========
鹅鹅鹅,曲项向天歌,白毛浮绿水,红草拨清波
=========分割后的古诗========
鹅鹅鹅
曲项向天歌
白毛浮绿水
红草拨清波
=========分割前的文件路径========
C:\aa\bb\cc
=========分割后的文件路径========
C:
aa
bb
cc
compareTo分析:
public class StringMethod3CompareTo {
public static void main(String[] args) {
String s1 = "xiaowang";
String s2 = "XIAOWANG";
//6.compareTo 比较来给你个字符串的大小,因为String有继承compare接口,所以可以比较
System.out.println(s1.compareTo(s2));// 返回:(a-A) = 32
//查看compareTo的底层:
// public int compareTo(String anotherString) {
// int len1 = value.length; //len1去接收第一个字符串的长度(s1长度:8)
// int len2 = anotherString.value.length; //len1去接收第一个字符串的长度(s1长度:8)
// int lim = Math.min(len1, len2); //取两个字符串中最小的长度(lim = 8)
// char v1[] = value; //用v1字符数组取接收第一个字符串s1
// char v2[] = anotherString.value; //用v1字符数组取接收第二个字符串s2
//
// int k = 0;
// while (k < lim) {//判断k和最小长度的大小,要是k的值小于最小长度,那么就执行下面代码,否则直接执行最后一句
// char c1 = v1[k];//取第一个字符数组的第一个字符
// char c2 = v2[k];//取第二个字符数组的第一个字符
// if (c1 != c2) {//判断所取的字符相等不,不相等直接跳到k++去自加
// return c1 - c2;//不相等就用第一个的ASCII码减去第二个的ASCII码,然后返回其值
// }
// k++;//接着将k自加,
// }
// return len1 - len2;//如果 k >= lim 后,那就直接返回第一个字符串的长度减去第二个字符串的长度
// }
//说白了,compareTo的过程就是,
// 1.先对比字符串中每一个对应的字符,相等就一直比较,直到不同或者比较晚都相同再处理
// 2.比较到不同的时候,就返回:前面字符串的那个字符的ASCII码去减去后面字符串那个字符的ASCII码
// 3.要是比较到某一个的最后一个字符,但是另外字符串还有字符的时候,
// 就直接返回:前面字符串的长度减去后面字符串的长度
// 4.要是两个字符串的值都相等,长度也相等,那么就返回0
}
}
输出:
32
format分析:
public class StringMethodFormat {
public static void main(String[] args) {
String name = "江仔";
int age = 22;
char sex = '男';
double score = 90/6;
//输出学生的信息
//方法1; 用字符串拼接 不是很方便更换信息
System.out.println("我是"+name+"今年"+age+"岁,性别"+sex+",得分"+score);
//方法2: 用format方法
String info = String.format("我是%s今年%d岁,性别%c,得分%.2f",name,age,sex,score);
System.out.println(info);
//方法3: 用一个String字符串去接收format
String formatStr = "我是%s今年%d岁,性别%c,得分%.2f";
String info2 = String.format(formatStr,name,age,sex,score);
System.out.println(info2);
//注意:
//1.%s %c %d %f 这些都是占位符 ,后面输入相应类型的变量就可以被替换
//2.%s 用字符串String替换
//3.%c 用字符char替换
//4.%d 用整数int替换
//5.%f 用小小数替换,可以去设定保留几位小数,%.2f保留两位,在.后面加上数字就代表保留几位
}
}
输出:
我是江仔今年22岁,性别男,得分15.0
我是江仔今年22岁,性别男,得分15.00
我是江仔今年22岁,性别男,得分15.00
13.3 StringBuffer类
13.3.1 StringBuffer 理解
基本介绍:
① StringBuffer是java.lang包下面的,代表可变的字符序列,可以对字符串内容进行增删
② 很多方法和String相同,但StringBuffer是可变长度的
③ StringBuffer是一个容器
实例介绍:
1.StringBuffer 的直接父类是 AbstractStringBuilder
2.StringBuffer 实现了 Serializable 接口,所以StringBuffer对象是可以串行化的
3.在父类中 AbstractStringBuilder 有char[] value属性,并且不是final修饰的,该 value 数组存放的字符串内容(这里便是 hello 字符串),是存放在堆中的
4.StringBuffer 是一个final类,所以不能被继承
5.因为StringBuffer的内容存放在 char value[] 中,所以在内容有变化的时(增删) 不用每一次都更换地址(也就是不用每一次都创建对象),所以效率高于String
13.3.2 String VS StringBuffer
① String保存的是字符串常量,里面的值不能被修改,每次String类的更新,实际上就是更改地址,效率很低
② StringBuffer 保存的是字符串变量,里面的值可以被修改,每次StringBuffer的更新,实际上都是在更新内容,不用更改地址,效率比较高
13.3.3 StringBuffer 构造器
- StringBuffer()
构造一个没有字符的字符串缓冲区,初始容量为16个字符。
- StringBuffer(int capacity)
构造一个没有字符的字符串缓冲区和指定的初始容量(capacity)。
- StringBuffer(String str)
构造一个初始化为指定字符串内容的字符串缓冲区。(数组长度为 内容长度 + 16)
13.3.4 String 和 StringBuffer 的转换
1.String —> StringBuffer
public class StringToStringBuffer {
public static void main(String[] args) {
//String ---> StringBuffer
String str = "hello";
//方法1: 使用构造器
//注:返回的对象才是StringBuffer类,而对于str是没有任何影响的
StringBuffer stringBuffer = new StringBuffer(str);
//方法2:使用append方法
//注:append方法是将其后面的参数的字符串,给附加到StringBuffer序列,可以是多种数据类型
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
}
}
- StrinBuffer ----> String
public class StringToStringBuffer {
public static void main(String[] args) {
//StrinBuffer ----> String
StringBuffer stringBuffer2 = new StringBuffer("xiaowang");
//方法1: 使用StringBuffer的toString方法
String s = stringBuffer2.toString();
//方法2: 使用构造器
String s1 = new String(stringBuffer2);
}
}
13.3.5 StringBuffer 常见方法
所有方法,可以通过查看API和查看StringBuffer的关系图去看
- 增:append()方法
注:可以增加各种类型的字符,在API中可以具体查看 - 删:delete(start,end) 方法
注:删除的是 start ~ end 中间的字符,索引从0开始,start包含,end不包含 - 改:replace(start,end,string)
注:修改的是 start ~ end 中间的字符,所要替换的可以不是具体删除字符个数的字符串 - 查:indexOf()
注:返回的是 所要查的字符第一次所出现的索引,没有返回-1 - 插:insert()
注:是在 所要插入索引处,去插入要插入的字符,原来字符自动后移 - 获取长度:length()
注:获取字符串的总长度
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("hello");
System.out.println("原字符串= "+stringBuffer);
//1.append(),注:可以增加各种类型的字符,在API中可以具体查看
stringBuffer.append(",");//"hello,"
stringBuffer.append("小王");//"hello,小王"
stringBuffer.append(100).append(true).append(52.21);//在这里是将其里面内容字符化了
System.out.println("append方法后= "+stringBuffer);//输出:hello,小王100true52.21
//2.delete() 注:删除的是 start ~ end 中间的字符,索引从0开始,start包含,end不包含
stringBuffer.delete(6,8);
System.out.println("delete方法后= "+stringBuffer);//输出:hello,100true52.21
//3.replace()方法 注:修改的是 start ~ end 中间的字符,所要替换的可以不是具体删除字符个数的字符串
stringBuffer.replace(5,9,"小王");
System.out.println("replace方法后= "+stringBuffer);
//4.查:indexOf() 注:返回的是 所要查的字符第一次所出现的索引,没有返回-1
System.out.println("indexOf方法后= "+stringBuffer.indexOf("小王"));//5
//5.插:insert() 注:是在 所要插入索引处,去插入要插入的字符,原来字符自动后移
System.out.println("insert方法后= "+stringBuffer.insert(5,"江仔"));
//6. length() 注:获取字符串的总长度
System.out.println(stringBuffer.length());
}
}
输出:
原字符串= hello
append方法后= hello,小王100true52.21
delete方法后= hello,100true52.21
replace方法后= hello小王true52.21
indexOf方法后= 5
insert方法后= hello江仔小王true52.21
18
13.3.6 StringBuffer 测试
A01:分析下列代码的输出,有错指明:
public class StringBufferExercise01 {
public static void main(String[] args) {
String str = null;//ok
StringBuffer sb = new StringBuffer();
sb.append(str);//查看底层源码,调用了父类AbstractStringBuilder的appendNull()方法
/* appendNull()方法:
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c; 字符数组长度就为 null的长度 4
return this;
}*///其本质就是将 null给字符化了,形成了字符数组,内容为‘n’,‘u’,‘l’,‘l’
System.out.println(sb.length());//输出4
System.out.println(sb);//null
StringBuffer sb1 = new StringBuffer(str);//str是一个空对象,查构造器底层源码:
/*StringBuffer(str)构造器
public StringBuffer(String str) {
super(str.length() + 16);//这里str是null,所以回报出空指针异常 NullPointException
append(str);
}
* */
System.out.println(sb1);//异常发生没有处理,所以不执行
}
}
输出:
4
null
Exception in thread "main" java.lang.NullPointerException
at java.lang.StringBuffer.<init>(StringBuffer.java:139)
at com.xiaowang.StringBuffer_.StringBufferExercise01.main(StringBufferExercise01.java:27)
Process finished with exit code 1
A02:
输入商品名称和商品价格,要求打印效果示例
商品名 商品价格
手机 123,456,78
要求:价格的小数点前面每三位用逗号隔开
public class StringBufferExercise02 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入商品名称:");
String name = scanner.next();
System.out.println("请输入商品价格:");
String price = scanner.next();
StringBuffer sbPrice = new StringBuffer(price);
//String转换成StringBuffer,方便调用StringBuffer的方法
for (int i =sbPrice.indexOf(".")-3 ; i >0 ; i-=3) {
//从小数点的索引开始去判断,先减了3后再去判断
sbPrice = sbPrice.insert(i , ",");//添加,
}
System.out.println("商品名称"+"\t"+"商品价格");//输出值
System.out.println(name+"\t\t"+sbPrice);
}
}
13.4 StringBuilder类
13.4.1 StringBuilder理解
基本介绍:
1、是一个可变的字符序列,和 StringBuffer 的API是兼容的,但不保证同步(也就不是线程安全的,是单线程),该类被用作StringBuffer的替换,用在字符串缓冲区被单个线程使用的时候i(会更快)。
2、StringBuilder 的主要操作是:append(增)和 insert(插),方法是可以重载的,可以接收任意类型的数据
继承关系图:
说明是可以去简易替换StringBuffer的,而且主要用于单线程
实例介绍:
1.StringBuilder 继承了 AbstractStringBuilder类
2.实现了 Serializable ,说明StringBuilder对象是可以串行化的(对象可以网络传输,也可以保存到文件)
3.StringBuilder是 final类 ,所以不能被继承
4.StringBuilder 对象字符序列仍然存放在父类AbstractStringBuilder的
char[] value 属性中,所以也是在堆中
5. StringBuilder 的方法,没有做互斥处理,即没有 synchronized 关键字,因此在单线程情况下去使用
6.append的方法可能会触发扩容机制,扩容量:原来的长度(16)*2+2
13.5 String VS StringBuffer VS Stringbuilder
13.5.1 基本区别
13.5.2 效率测试
效率:
StringBuilder > StringBuffer >String
案例测试:
//通过让三个类去执行同一个拼接的方法,去统计其所用的时间去比较其效率
public class StringVSStringBufferVSStringbuilder {
public static void main(String[] args) {
//1.String类效率测试
String str = " ";//定义一个空串
long startTime = 0l;//存放开始时间
long endTime = 0l;//存放结束时间
startTime = System.currentTimeMillis();//获取当前时间去赋值给开始时间
for (int i = 0; i < 20000; i++) {//拼接20000次
str = str + i;
}
endTime = System.currentTimeMillis();//获取完成后时间
System.out.println("String所执行的时间"+(endTime - startTime));
//2.StringBuffer类效率测试
StringBuffer stringBuffer = new StringBuffer(" ");//定义一个StringBuffer对象
startTime = System.currentTimeMillis();//获取当前时间去赋值给开始时间
for (int i = 0; i < 20000; i++) {//拼接20000次
stringBuffer.append((i));
}
endTime = System.currentTimeMillis();//获取完成后时间
System.out.println("StringBuffer所执行的时间"+(endTime - startTime));
//2.StringBuilder类效率测试
StringBuilder stringBuilder = new StringBuilder(" ");//定义一个StringBuffer对象
startTime = System.currentTimeMillis();//获取当前时间去赋值给开始时间
for (int i = 0; i < 20000; i++) {//拼接20000次
stringBuilder.append((i));
}
endTime = System.currentTimeMillis();//获取完成后时间
System.out.println("StringBuilder所执行的时间"+(endTime - startTime));
}
}
输出:
String所执行的时间1013
StringBuffer所执行的时间1
StringBuilder所执行的时间0
总结:
效率:StringBuilder > StringBuffer >String,每一次的运行时间是不一样的,但是呢,StringBuilder的安全系数不高
13.5.3 三者选择
13.6 Math类
1、基本介绍
Math类用于计算数学中的运算方法,因为方法都是static静态的,所以直接用Math.方法名可以使用
2、常用方法
1.abs 求绝对值
2.pow 求幂
3.ceil 向上取整
4.floor 向下取整
5.round 四舍五入
6.sqrt 开平方
7.random 获取随机数
8.max 求两个数的最大值
9.min 求两个数的最小值
案例理解:
public class MathMethod {
public static void main(String[] args) {
//1.abs 求绝对值
int abs = Math.abs(-9);//括号里面可以是多种数据类型
System.out.println(abs);//输出:9
//2.pow 求幂
double pow = Math.pow(12,2);//只能是double类型
System.out.println(pow);//输出:144.0
//3.ceil 向上取整
double ceil = Math.ceil(12.3);//只能是double类型
System.out.println(ceil);//输出:13.0
//4.floor 向下取整
double floor = Math.floor(12.3);//只能是double类型
System.out.println(floor);//输出:12.0
//5.round 四舍五入,有long(接近long) 和 float(接近int) 类型的round方法,
long round = Math.round(13.254);//数据中可以是float和double数据类型
System.out.println(round);//输出:13
//6.sqrt 开平方
double sqrt = Math.sqrt(12.5);//只能是double类型
System.out.println(sqrt);//输出:3.5355339059327378
//7.random 获取随机数
double random = Math.random();//返回值为 double值为正号,大于等于 0.0 ,小于 1.0 。
System.out.println(random);
int random1 = (int) Math.random();//随机生成int随机数,用强制转换,转换了都是0
System.out.println(random1);
//8.max 求两个数的最大值
System.out.println(Math.max(1.2,1.5));//可以是多种数据类型,输出:1.5
//9.min 求两个数的最小值
System.out.println(Math.min(1.2,1.5));//可以是多种数据类型,输出:1.2
//获取a-b之间的随机数,例子:返回2--7之间的随机整数
for (int i = 0; i <10 ; i++) {
System.out.println((int)(2+Math.random()*(7-2+1)));
}
}
}
获取随机数加强:
随机生成a-b之间的随机整数,例如(2-7)之间是整数
public class MathMethod {
public static void main(String[] args) {
//获取a-b之间的随机数,例子:返回2--7之间的随机整数
//分析:
//1. Math.random()是获取0-1之间的随机小数,0可取,1不可取, 0 <= Math.random() < 1
//2.获取a-b之间是随机数,就: a + (Math.random()*(b-a+1))
// 分析: 因为 0 <= Math.random() < 1 ,(b-a)< (b-a+1) < (b-a+1)
// 所以:0*(b-a) <= Math.random()*(b-a+1) < 1*( b-a+1 )
// 所以:0 <= Math.random()*(b-a+1) < (b-a+1)
// 又因为:a+0 <= Math.random()*(b-a+1) < a+(b-a+1)
// 所以:a <= Math.random()*(b-a+1) < b+1
//3.要想获得整数,就需要用 int 去强制转换所获得的随机值,单纯 (int)Math.random()=0
//4.所以要想获得a-b之间的随机整数就:(int)(a + (Math.random()*(b-a+1)))
//5.用a=2,b=7,循环十次来测试是否正确
for (int i = 0; i <10 ; i++) {
System.out.print(((int)(2+Math.random()*(7-2+1)))+"\t");
//用制表位去改输出格式,就不用换行输出了
}
}
}
输出:
2 2 4 7 3 7 7 2 7 6
13.7 Arrays类
基本介绍:
包含一系列静态方法,用于管理或操作数组(比如排序或者搜索)
13.7.1常用方法:
13.7.1.1 toString()方法
从API可以看出来,toString方法可以输出多种数据类型的数组
案例演示:
比较for循环和toString方法
public class ArraysMethod {
public static void main(String[] args) {
Integer [] integer = {1,20,15,50};
//1.遍历数组
//老方法for循环
System.out.println("======for循环======");
for (int i = 0; i <integer.length ; i++) {
System.out.print(integer[i]+"\t");//制表位分开
}
System.out.println();
//toString方法
System.out.println("======toString循环======");
System.out.println(Arrays.toString(integer));
//查看toString底层源码:
/*
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {//其实也是用了for循环
b.append(String.valueOf(a[i]));//将其每一个字符用valueOf去字符化,然后用append去拼接
if (i == iMax)
return b.append(']').toString();
b.append(", ");//中间用 , 隔开
}*/
}
}
输出:
======for循环======
1 20 15 50
======toString循环======
[1, 20, 15, 50]
13.7.1.2 sort排序
① 是从小到大排序,从API可以看出sort方法可以有很多数据类型,每一种数据类型都有两种排序方式,一个是整个数组进行排序,另一个是指定某个索引范围内的数进行排序
int为例:
案例分析:
public class ArraysMethodSort {
public static void main(String[] args) {
Integer [] arr={1,-1,0,5,3};
//1.sort整体排序
Arrays.sort(arr);//数组是引用型的,所以排序过后,会影响原始的integer数组顺序
System.out.println("======sort排序后数组=====");
System.out.println(Arrays.toString(arr));//用toString方法去输出
//2.sort指定范围排序(注意:包含前面不包含后面)
Integer [] arr1 = {-1,8,5,9,3,2,6};
Arrays.sort(arr1,1,6);//索引为1 <= arr1[i] < 6之间的数组排序:
//所以输出:-1,2,3,5,8,9,6
System.out.println(Arrays.toString(arr1));
}
}
输出:
======sort排序后数组=====
[-1, 0, 1, 3, 5]
[-1, 2, 3, 5, 8, 9, 6]
② 定制排序
案例分析:
public class ArraysMethodSort {
public static void main(String[] args) {
//3.定制排序:(自己通过debug去调试去深刻理解)
//主要是判断public int compare(Object o1, Object o2)方法的返回值是 >0 还是 <0,
//这就会影响最后的排序方法(从大到小,还行,从小到大)
Integer [] arr2 = {1,9,5,6,2,4,7};
Arrays.sort(arr2, new Comparator() {//new Comparator是一个接口,整个是一个匿名内部类
@Override
public int compare(Object o1, Object o2) {//当执行的时候,就会进入compare底层代码
Integer i1 = (Integer) o1;//向下转型
Integer i2 = (Integer) o2;
return i1-i2;//这里可以通过(i1-i2),或者(i2-i1)的大小去判断排序的方式(从大到小还是从小到大)
}
});
}
}
输出:
i1-i2的结果:[1, 2, 4, 5, 6, 7, 9]
i2-i1的结果:[9, 7, 6, 5, 4, 2, 1]
定制排序比较难以理解,我们通过自己去写一个定制排序来进一步理解:
import java.util.Arrays;
import java.util.Comparator;
public class ArraysExercise {
public static void main(String[] args) {
int [] arr = {1,5,6,0,8,4};
// bubbleSort(arr);
customSort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {//o1,o2也就是后面要传入的arr[j],arr[j+1]
int i1 = (Integer)o1;//先拆箱将其int化
int i2 = (Integer)o2;
return i1-i2;//在这里可以是return i2-i1;
//return i2-i1;
// 在这里的值,就相当于是c.compare(arr[j],arr[j+1])方法 的大小判断了
}
});
System.out.println(Arrays.toString(arr));
}
//化繁为简:
//1.数组排序平常都是用冒泡排序(但是当要修改排序方式的时候,只能去修改方法中的代码去改变不方便)
public static void bubbleSort(int [] arr){
int temp = 0;//中间变量
for (int i = 0; i < arr.length-1; i++) {//因为外层要少循环一次底层
for (int j = 0; j < arr.length-1-i; j++) {//内层循环
//从小到大排序(要是从大到小:(arr[j] < arr[j+1])即可)
if (arr[j] > arr[j+1]){
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
//定制排序(通过Comparator接口就可以直接实现排序方法)
public static void customSort(int [] arr, Comparator c){
int temp1 = 0;//中间变量
for (int i = 0; i < arr.length-1; i++) {//因为外层要少循环一次底层
for (int j = 0; j < arr.length-1-i; j++) {//内层循环
//主要确定排序方式的就是if中的语句,
// 所以将其改成接口,通过匿名内部类去确定排序方式更加方便
if (c.compare(arr[j],arr[j+1])>0){//如果c.compare(arr[j],arr[j+1])>0则从小到大,反之从大到小
temp1 = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp1;
}
}
}
}
}
总结:指定排序的方法主要体现了接口编程的思想,这样是很方便的。
13.7.1.3 binarySearch 二分法
13.7.1.4 copyOf 数组元素的复制
13.7.1.5 fill数组元素填充
13.7.1.6 equals 比较两个数组
13.7.1.7 asList 转换集合
以上方法统统用案例演示:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
public class ArraysMethod01 {
public static void main(String[] args) {
Integer [] arr = {1,2,3,5,6};
// 3.binarySearch二分法查找,必须是排好序的数组去使用,可以正序可以负序
// 注: 要查找的数据前面的数据保证是有顺序的就行
// 如果数组中存在该元素就输出索引,
System.out.println(Arrays.binarySearch(arr,2));//输出:1
//如果数组中不存在,就返回 -(low + 1); low是应该存在位置,通过底层去查看
System.out.println(Arrays.binarySearch(arr,4));//-(3+1)==-4
//4.copyOf 数组元素的复制
// Arrays.copyOf(arr,arr.length);
// arr是所要去拷贝的数组,arr.length是确定拷贝多少个数据
// 如果所要拷贝的数据长度不够,那就用null去填充
// 如果arr.length = 0,则返回空数组
// 如果arr.length < 0,则抛出NegativeArraysSizeException异常
// 注:copyOf方法,底层使用的方法是:arraycopy
Integer [] arr1 = Arrays.copyOf(arr,0);
System.out.println("拷贝后arr1="+Arrays.toString(arr1));
//5.fill数组元素填充
// 5.1 Arrays.fill(arr2,num);填充后的结果是将数组中所有的数据改成所填充的num
// 5.2 Arrays.fill(arr2,fromIndex,toIndex,num);将fromIndex,toIndex之间的数给替换成num
Integer [] arr2 = {1,2,3,4};
System.out.println("填充前的arr2=" +Arrays.toString(arr2));
Arrays.fill(arr2,0,2,20);//将索引0-2之间的数替换成20
System.out.println("部分填充后的arr2="+Arrays.toString(arr2));
Arrays.fill(arr2,20);//所有的都替换成num = 20
System.out.println("填充后的arr2="+Arrays.toString(arr2));
//6.equals 比较两个数组内容是否完全一样
Integer [] arr3 = {1,2,3};
Integer [] arr4 = {1,2,3};
boolean equals = Arrays.equals(arr3,arr4);//用一个boolean变量去接收判断后的结果
System.out.println(equals);//相同返回ture,不同返回false
//7.asList 将一组值,转换成list(集合)
// 7.1 asList方法会把(1,2,3,4,5)转换成一个集合
// 7.2 asLIst的编译类型是:List(接口类)
// 7.3 asList的运行类型是:java.util.Arrays$ArrayList
// 是Arrays类里面的一个静态内部类:底层:
// private static class ArrayList<E> extends AbstractList<E>
// implements RandomAccess, java.io.Serializable
List asList = Arrays.asList(1,2,3,4,5);
System.out.println("asList="+asList);
System.out.println("asList的运行类型="+asList.getClass());
}
}
输出:
1
-4
拷贝后arr1=[]
填充前的arr2=[1, 2, 3, 4]
部分填充后的arr2=[20, 20, 3, 4]
填充后的arr2=[20, 20, 20, 20]
true
asList=[1, 2, 3, 4, 5]
asList的运行类型=class java.util.Arrays$ArrayList
13.7.1.7 Arrays练习题
题目:
定义Book类,包含name和price,按price两种方式排序,按书名长度排序,,Book类有4个对象
book[0]=new Book(“红楼梦新版”,100);
book[1]=new Book(“西游记”,80);
book[2]=new Book(“水浒传20年”,120);
book[3]=new Book(“三国演义”,50);
- 提示:可以通过Comparator接口匿名内部类,也就是定制排序*/
分析:
1.为了方便切换从大到小还是从小到大,我们使用Comparator接口去实现。
2.我们先自己去写接口编程方法去实现价格的排序:
package com.xiaowang.arrays;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
/*定义Book类,包含name和price,按price两种方式排序,,Book类有4个对象
* 提示:可以通过Comparator接口匿名内部类,也就是定制排序*/
public class ArraysExercise {
public static void main(String[] args) {
Book [] book = new Book[4];
book[0]=new Book("红楼梦新版",100);
book[1]=new Book("西游记",80);
book[2]=new Book("水浒传20年",120);
book[3]=new Book("三国演义",50);
//因为是通过price去排序,book是Book类,price是double类,所以用double数组去存放book的价格
double [] price = new double[4];
for (int i = 0; i < book.length; i++) {
price[i]=book[i].getPrice();
}
customSort(price, new Comparator() {
@Override
public int compare(Object o1, Object o2) {//o1,o2也就是后面要传入的arr[j],arr[j+1]
double i1 = (Double) o1;//先拆箱将其int化
double i2 = (Double) o2;
// return ((int)i1)-((int)i2);//从小到大
return ((int)i2)-((int)i1);//从大到小
//因为是重写判断price对象相减的值,所以我们需要去强制转换数据类型
// 在这里的值,就相当于是c.compare(arr[j],arr[j+1])方法 的大小判断了
}
});
System.out.println(Arrays.toString(price));
}
//定制排序(通过Comparator接口就可以直接实现排序方法)
public static void customSort(double [] arr, Comparator c){
double temp1 = 0;//中间变量
for (int i = 0; i < arr.length-1; i++) {//因为外层要少循环一次底层
for (int j = 0; j < arr.length-1-i; j++) {//内层循环
//主要确定排序方式的就是if中的语句,
// 所以将其改成接口,通过匿名内部类去确定排序方式更加方便
if (c.compare(arr[j],arr[j+1])>0){//如果c.compare(arr[j],arr[j+1])>0则从小到大,反之从大到小
temp1 = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp1;
}
}
}
}
}
class Book{
private String name ;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public double getPrice() {
return price;
}
}
输出:
[120.0, 100.0, 80.0, 50.0]
此时只有价格被排序输出,同理也可以使用此方法去通过书名长度去排序,但是。我们的要求根据价格或者书名长度去输出是整个书的信息,所以判断对象得要是Book对象,所以我们可以在刚刚的基础上去修改代码
如果每一次我们都自己去写定制排序的内部结构的话,也是很麻烦的,而且本来Java就有定制排序的方法,我们直接用就可以
package com.xiaowang.arrays;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
public class ArraysExercise01 {
public static void main(String[] args) {
Book1 [] book1 = new Book1[4];
book1[0]=new Book1("红楼梦新版",100);
book1[1]=new Book1("西游记",80);
book1[2]=new Book1("水浒传2020年",120);
book1[3]=new Book1("三国演义",50);
//使用Arrays自己有的定制排序方法
// 1. price排序
Arrays.sort(book1, new Comparator<Book1>() {
@Override
public int compare(Book1 o1, Book1 o2) {//这里o1,o2传入的是book对象
return ((int)o1.getPrice())-((int)o2.getPrice());//从小到大排序
// return ((int)o2.getPrice())-((int)o1.getPrice());//从大到小排序
}
});
System.out.println("==price排序==");
System.out.println(Arrays.toString(book1));
//1.name长度排序
Arrays.sort(book1, new Comparator<Book1>() {
@Override
public int compare(Book1 o1, Book1 o2) {
return o1.getName().length()-o2.getName().length();//名字长度从短到长
// return o2.getName().length()-o1.getName().length();//名字长度从长到短
}
});
System.out.println("==name排序==");
System.out.println(Arrays.toString(book1));
}
}
class Book1{
private String name;
private double price;
public Book1(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Book1{" +
"name='" + name + '\'' +
", price=" + price +
'}'+'\n';
}
}
输出:
==price排序==
[Book1{name='三国演义', price=50.0}
, Book1{name='西游记', price=80.0}
, Book1{name='红楼梦新版', price=100.0}
, Book1{name='水浒传2020年', price=120.0}
]
==name排序==
[Book1{name='西游记', price=80.0}
, Book1{name='三国演义', price=50.0}
, Book1{name='红楼梦新版', price=100.0}
, Book1{name='水浒传2020年', price=120.0}
]
总结:
Arrays的sort方法,真的很有用,特别是Comparator接口编程的sort方法,减少了很多时间。
13.8 System类
13.8.1 常见方法
1.exit 退出当前程序
案例:
import java.util.Arrays;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
public class System_ {
public static void main(String[] args) {
//1.exit 退出当前程序
System.out.println("xiaowang");
System.exit(0);//0 表示一个状态,是正常状态
System.out.println("jiangzai");//因为前面退出了当前程序,所以下面的jiagnzai不会被执行
}
}
输出:
xiaowang
2.arraycopy:复制数组元素
案例:
import java.util.Arrays;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
public class System_ {
public static void main(String[] args) {
//2.arraycopy:复制数组元素,比较合适底层调用
/*arraycopy方法底层源码:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);*/
/*五个参数解读:
src – the source array. 源数组
srcPos – starting position in the source array. 从源数组的哪个索引开始拷贝
dest – the destination array. 目标数组,即要把源数组数据拷贝到哪个数组
destPos – starting position in the destination data. 把源数组的数据拷贝到目标数组哪个索引
length – the number of array elements to be copied. 需要拷贝的个数(不能超过源数组的个数)
*/
int [] arr={1,2,3};
int [] arr1 = new int[3];
System.arraycopy(arr,1,arr1,1,2);//应该输出:[0,2,3]
System.out.println("拷贝后的数组arr1="+Arrays.toString(arr1));
}
}
输出:
拷贝后的数组arr1=[0, 2, 3]
3.currentTimeMillis()
案例:
package com.xiaowang.system_;
import java.util.Arrays;
/**
* @Author 小王
* @DATE: 2022/4/9
*/
public class System_ {
public static void main(String[] args) {
//3.currentTimeMillis() 返回当前时间,距离(1970-1-1)年的毫秒数
System.out.println(System.currentTimeMillis());
}
}
4.gc 虚拟机回收
解析:
13.9 BigInteger和BigDecimal类
基本介绍:
1、BigInteger 适合保存较大的整型
常用方法案例:
package com.xiaowang.bigNum_;
import java.math.BigInteger;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class BigInteger_ {
public static void main(String[] args) {
//long l = 1234567899999999999999999999999;//超出了long的范围
//平时定义一个大的数的时候,一般都是long类型,但是当我们的数大于long了后,就需要用BigInteger
//1.BigInteger因为是一个类,所以在定义一个超级大的数的时候,用对象去表示
//2.要注意,当去创建BigInteger对象的时候,里面的值要用字符串去表示,在底层会去自动转换成整型
BigInteger bigInteger1 = new BigInteger("1234567899999999999999999999999");
BigInteger bigInteger2 = new BigInteger("1000");
//3.BigInteger可以做加减乘除,但是呢,因为这是一个类对象,所以是不可以直接用 + - * /这些去直接做运算的
//4.所以BigInteger的运算就直接调用BigInteger类对象相应的方法去处理就好,可以查看体系图了解BigInteger的方法
//5. add()加法
System.out.println("加法:"+bigInteger1.add(bigInteger2));
//6. subtract() 减法
System.out.println("减法:"+bigInteger1.subtract(bigInteger2));
//7. multiply() 乘法
System.out.println("乘法:"+bigInteger1.multiply(bigInteger2));
//8. divide() 除法
System.out.println("除法:"+bigInteger1.divide(bigInteger2));
}
}
输出:
加法:1234567900000000000000000000999
减法:1234567899999999999999999998999
乘法:1234567899999999999999999999999000
除法:1234567899999999999999999999
2、BigDecimal适合保存精度更高的浮点型(小数)
常用方法案例:
package com.xiaowang.bigNum_;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class BigDecimal_ {
public static void main(String[] args) {
double num = 1234567899999999999999.9999999;//很明显double数值的大小超过了double的最大数,所以会被截取一段输出
System.out.println("double类型的num:"+num);//输出:1.2345679E21
//平时定义一个大的小数的时候,一般都是double类型,但是当我们的数大于double了后,就需要用BigDecimal
//1.BigDecimal因为是一个类,所以在定义一个超级大的数的时候,用对象去表示
//2.要注意,当去创建BigDecimal对象的时候,里面的值要用字符串去表示,在底层会去自动转换成浮点型
BigDecimal bigDecimal1 = new BigDecimal("1234567899999999999999.9999999");
BigDecimal bigDecimal2 = new BigDecimal("100.0001");
//3.BigDecimal可以做加减乘除,但是呢,因为这是一个类对象,所以是不可以直接用 + - * /这些去直接做运算的
//4.所以BigDecimal的运算就直接调用BigDecimal类对象相应的方法去处理就好,可以查看体系图了解BigDecimal的方法
//5. add()加法
System.out.println("加法:"+bigDecimal1.add(bigDecimal2));
//6. subtract() 减法
System.out.println("减法:"+bigDecimal1.subtract(bigDecimal2));
//7. multiply() 乘法
System.out.println("乘法:"+bigDecimal1.multiply(bigDecimal2));
//8. divide() 除法
// System.out.println("除法:"+bigDecimal1.divide(bigDecimal2));//会有异常(除不尽,有无限小数)
//注意:对于divide()除法的时候,有可能会抛出异常:
// ArithmeticException: Non-terminating decimal expansion(除不尽异常)
//为了使其能够正常运行,我们可以通过BigDecimal.ROUND_CEILING去完成
//BigDecimal.ROUND_CEILING解读:
//这个的意思是,保留分母的小数位数,也就是,分母有几位,得出来的结果就是保留几位小数
System.out.println("除法:"+bigDecimal1.divide(bigDecimal2,BigDecimal.ROUND_CEILING));
System.out.println("除法:"+bigDecimal1.divide(bigDecimal2));//会有异常(除不尽,有无限小数)
}
}
输出:
double类型的num:1.2345679E21
加法:1234567900000000000100.0000999
减法:1234567899999999999899.9998999
乘法:123456913456789999999999.99998999999
除法:12345666654333345666.6543334
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.xiaowang.bigNum_.BigDecimal_.main(BigDecimal_.java:35)
13.10 日期类
13.10.1 第一代 Date类
Date类是第一代时间类
构造器:
方法案例分析:
package com.xiaowang.date_;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class Date01 {
public static void main(String[] args) throws ParseException {
//1.获取当前系统的时间,(
// 1.1 使用Date()构造器)
Date date1 = new Date();
System.out.println("Date当前日期:"+date1);
// 1.2 使用Date(long)构造器
Date date2 = new Date(123456);//里面传的是long 毫秒数
//2.这里的Date是在 java.util.Date包里面,不是 java.sql.Date(这个是链接数据库时候的)
// 3.默认的输出日期格式,是国外的输出方式,因此通常都要转换格式
// 3.1 创建SimpleDateFormat对象,可以指定相应的输出格式
// 3.2 相应格式是有h标准规定的,字母不能乱写,可以去查API了解每个字母的含义
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss E");
System.out.println("转换后日期:"+simpleDateFormat.format(date1));//format:将日期转换为指定格式的字符串
//4.可以把格式化的String字符串,转换成对应的Date
// 注:所需要转换的String字符串时间的格式,必须要和你定义的SimpleDateFormat类对象的格式一样,不然要报错
String date = "2022年04月10日 14:50:11 星期日";//必须和上面SimpleDateFormat类对象的时间格式一样
Date date3 = simpleDateFormat.parse(date);//这里会抛出一个转换异常,所以我们通常要去接收这个异常用throws
System.out.println("转换后时间:"+date3);//这时的转换是国外的输出格式,所以可以再用一次format去指定格式
System.out.println("格式转换后时间:"+simpleDateFormat.format(date3));//
}
}
输出:
Date当前日期:Sun Apr 10 14:57:53 CST 2022
转换后日期:2022年04月10日 14:57:53 星期日
转换后时间:Sun Apr 10 14:50:11 CST 2022
格式转换后时间:2022年04月10日 14:50:11 星期日
注:
① 这里的Date是在 java.util.Date包里面,不是 java.sql.Date(这个是链接数据库时候的)
② 创建SimpleDateFormat对象,相应格式是有标准规定的,字母不能乱写,可以去查API了解每个字母的含义
③ 所需要转换的String字符串时间的格式,必须要和你定义的SimpleDateFormat类对象的格式一样,不然要报错
13.10.2 第二代 Calendar类
Calendar是第二类时间类
构造器:
字段:
Calendar的字段超级多,使用的时候最好去查API
案例分析:
package com.xiaowang.date_;
import java.util.Calendar;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class Calendar_ {//日历类
public static void main(String[] args) {
//1.Calendar是一个抽象类,并且是private私有的
//2.可以通过 getInstance()方法来获取实例
//3.Calendar提供了大量的方法和字段去使用,可以查看Calendar底层或者API
//实例演示:
//1.获取实例对象
//Calendar calendar1 = new Calendar();//因为是抽象类,所以通过new一个对象就是错的
Calendar calendar = Calendar.getInstance();//因为是抽象的,所以通过方法来获取实例
System.out.println(calendar);//这里输出的是Calendar的所有字段
//2.获取某个日历对象的某个日历字段
//通过Calendar对象去调用get(Calendar.字段名)方法去获取
//Calendar有很多字段,可以通过API去查每一个字段的意思
System.out.println("Calendar年:"+calendar.get(Calendar.YEAR));
//注意:获取月份的时候,要+1,因为month是从0开始编号的
System.out.println("Calendar月:"+(calendar.get(Calendar.MONTH)+1));
System.out.println("Calendar日:"+calendar.get(Calendar.DAY_OF_MONTH));
//注意:获取小时的时候,有12小时的,也有24小时的
System.out.println("Calendar时:"+calendar.get(Calendar.HOUR));//Calendar.HOUR 是 12小时
System.out.println("Calendar时:"+calendar.get(Calendar.HOUR_OF_DAY));//Calendar.HOUR_OF_DAY 是 24小时
//3.Calendar没有专门的格式转换方法,一般都是程序员自己去设定
System.out.println(calendar.get(Calendar.YEAR)+"年"+(calendar.get(Calendar.MONTH)+1)+"月"+calendar.get(Calendar.DAY_OF_MONTH)+"日");
}
}
注:
1.Calendar是一个抽象类,并且是private私有的,通常通过 getInstance()方法来获取实例
2.获取月份的时候,要+1,因为month是从0开始编号的
3.Calendar没有专门的格式转换方法,一般都是程序员自己去设定
4.Calendar.HOUR 是 12小时
5. Calendar.HOUR_OF_DAY 是 24小时
13.10.3 第三代日期类
为什么会出现第三代日期类?
第三类日期类是在jdk8后才出现的
13.10.3.1日期时间类
第三代是在 java.time.包里面
第三代的体系图很复杂,同时实现了很多接口,所以所包含的方法也很多
案例方法分析:
package com.xiaowang.date_;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class LocalDate_ {
public static void main(String[] args) {
//1.使用now()方法去获得当前日期及时间的对象
LocalDateTime localDateTime = LocalDateTime.now();//获得当前日期及时间的对象
System.out.println("格式化前的时间:"+localDateTime);
LocalDate localDate = LocalDate.now();//获取当前日期对象,也就是只获得 年月日 同时没有获取时分秒的方法
LocalTime localTime = LocalTime.now();//获取当前时间对象,也就是只获得 时分秒 同时没有获取年月日的方法
//2.仅仅只要获取某一个字段,那么直接用get相关方法就行,在diagrams体系图中可以查看所有方法,也可以查看API
System.out.println("年"+localDateTime.getYear());
System.out.println("月"+localDateTime.getMonth());//获取的是英文月份
System.out.println("月"+localDateTime.getMonthValue());//获取的是中文数字月份
System.out.println("日"+localDateTime.getDayOfMonth());//获取这个月的某一天
System.out.println("时"+localDateTime.getHour());//获取的是24小时的时间
System.out.println("分"+localDateTime.getMinute());
System.out.println("秒"+localDateTime.getSecond());
}
}
输出:
格式化前的时间:2022-04-10T16:16:34.746
年2022
月APRIL
月4
日10
时16
分16
秒34
13.10.3.2格式化时间类
DateTimeFormatter类是java.time.format包里面的一格式化时间的一个类,是用DateTimeFormatter类对象去调用其format方法去格式化,
DateTimeFormatter类对象的获取是:
格式化对象方法是
案例分析:
package com.xiaowang.date_;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class LocalDate_ {
public static void main(String[] args) {
//1.使用now()方法去获得当前日期及时间的对象
LocalDateTime localDateTime = LocalDateTime.now();//获得当前日期及时间的对象
System.out.println("格式化前的时间:"+localDateTime);
//3.格式化时间
// 使用DateTimeFormatter对象去格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//使用指定的模式创建格式化程序。
String format = dateTimeFormatter.format(localDateTime);//返回后的字符串对象才是格式化过后的
System.out.println("格式化后的时间:"+format);
}
}
输出:
格式化前的时间:2022-04-10T16:26:03.098
格式化后的时间:2022年04月10日 16时26分03秒
13.10.3.3 instant 时间戳
instant是在java.time 包里面的
案例分析;
package com.xiaowang.date_;
import java.time.Instant;
import java.util.Date;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
public class Instant_ {
public static void main(String[] args) {
//1.通过静态方法 now()去获取instant对象实例
Instant now = Instant.now();
System.out.println(now);//返回当前时间点的时间戳
//2.如果要将instant类对象转换成一个Date对象就是用from()方法即可
Date date = Date.from(now);
System.out.println(date);//返回当前时间的Date对象
//3.如果将Date对象转换成一个instant对象就用toInstant()方法即可
Instant instant = date.toInstant();
System.out.println(instant);
}
}
输出:
2022-04-10T08:35:05.827Z
Sun Apr 10 16:35:05 CST 2022
2022-04-10T08:35:05.827Z
注意:第三类日期类有很多很多的方法,使用的时候,可以通过API去查看每一个方法的使用。
13.11 练习题
13.11.1 A01:String翻转
题目:
将字符串指定部分进行反转,比如将“abcde”,反转成“aedcb”,
要求:写一个方法去完成
public static String reverse(String str,int startIndex,int endIndex)
分析:
package com.xiaowang.homWork_;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
/*将字符串指定部分进行反转,比如将“abcde”,反转成“aedcb”,
* 要求:写一个方法去完成
* public static String reverse(String str,int startIndex,int endIndex)*/
public class Homework01 {
public static void main(String[] args) {
String str ="abcde";
System.out.println("交换前="+str);
str = reverse("abcde", 1, 4);
//因为是要将iyge字符串给翻转,所以将翻转后的还是赋值给本字符串,就不用再去new一个String
System.out.println("翻转后="+str);
}
public static String reverse(String str, int startIndex, int endIndex) {
//1.先将字符串转换成一个数组,因为String是不可以直接修改的,
char [] arrstr = str.toCharArray();
//2.用一个char字符去做中间变量
char temp = ' ';
//3.用for循环去翻转数组间的字符
for (int i = startIndex,j = endIndex; i < j; i++,j--) {
temp = arrstr[i];
arrstr[i] = arrstr[j];
arrstr[j] = temp;
}
//4.然后再将字符数组转换成String返回
String str1 = String.valueOf(arrstr);
return str1;
}
}
以上方法虽然可以实现功能,但是不稳定,不坚固,当所传入的数组为null,或者startIndex和endIndex越界什么的就会直接报错,所以,为了避免这些错误,我们需要去考虑找方法使其代码更加稳固。
修改后的代码:
package com.xiaowang.homWork_;
/**
* @Author 小王
* @DATE: 2022/4/10
*/
/*将字符串指定部分进行反转,比如将“abcde”,反转成“aedcb”,
* 要求:写一个方法去完成
* public static String reverse(String str,int startIndex,int endIndex)*/
public class Homework01 {
public static void main(String[] args) {
String str ="abcde";
System.out.println("交换前="+str);
try {
str = reverse("abcde", 1, 4);//因为这里可能会有异常,所以我们去处理一下
} catch (NullPointerException e) {
System.out.println(e.getMessage());//输出我们的异常信息
return;//出错了就不往下面执行了
} catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
return;
}
//因为是要将iyge字符串给翻转,所以将翻转后的还是赋值给本字符串,就不用再去new一个String
System.out.println("翻转后="+str);
}
public static String reverse(String str, int startIndex, int endIndex) {
//为了使代码更稳固我们需要去处理一些异常,
// 从正确的想法去取反就可以包括所有的错误想法
/*整体作为一个异常
if (!(str != null && startIndex<endIndex && endIndex<str.length())){
throw new RuntimeException("参数不正确");
//也可以将具体的异常类型写出来,然后相应的去输出
}*/
//空指针异常
if (!(str!=null)){
throw new NullPointerException("空指针异常");
} //越界异常
else if (!(startIndex<endIndex && endIndex<str.length())){
throw new ArrayIndexOutOfBoundsException("越界异常");
}
//1.先将字符串转换成一个数组,因为String是不可以直接修改的,
char [] arrstr = str.toCharArray();
//2.用一个char字符去做中间变量
char temp = ' ';
//3.用for循环去翻转数组间的字符
for (int i = startIndex,j = endIndex; i < j; i++,j--) {
temp = arrstr[i];
arrstr[i] = arrstr[j];
arrstr[j] = temp;
}
//4.然后再将字符数组转换成String返回
String str1 = String.valueOf(arrstr);
return str1;
}
}
13.11.2 A02:注册登录
题目:
用户输入用户名,密码,邮箱去注册一个账号,要是不满足以下要求,就返回相应的异常信息
要求,
1.用户名只能是2或3或4长度
2.密码只能是6位,并且全为数字
3.邮箱必须有@和. 并且@要在.的前面,并且都不能在首位
分析:
package com.xiaowang.homWork_;
import java.util.Scanner;
/**
* @Author 小王
* @DATE: 2022/4/11
*/
/*思路:
* 1.化繁为简,先写死的字符串去实现
* 2.先写一个方法去判断所要输入的信息是否正确
* 3.再写每一个判断所需要用到的方法(1.判断是否全为数字 2.@和.的存在和顺序),在第二步判断中直接去调用具体的判断方法
* 4.每写完一个判断方法就在main中去用字符串去测试一下
* 5.用字符串测试没问题后,再去用Scanner去接收再去测试
* 6.如果要求更多也可以在先有基础上继续加条件去校验
* */
public class HomeWork02 {
public static void main(String[] args) {
System.out.println("请输入你的用户名,密码和邮箱");
Scanner scanner = new Scanner(System.in);
String name = scanner.next();
String password = scanner.next();
String email = scanner.next();
try {
message(name,password,email);
} catch (Exception e) {
System.out.println(e.getMessage());
return;//有错输出就不再执行
}
System.out.println("注册成功");
}
//写一个方法来做判断
public static void message(String name,String password,String email){
if (!(name.length()>=2 && name.length()<=4)){//判断用户名长度
throw new RuntimeException("用户名长度错误");
}else if (!(password.length()==6 && checkStrIsNum(password))){
throw new ArithmeticException("密码长度不正确或者不全为数字");
}else if (!(checkStrIsEmail(email))){
throw new NumberFormatException("邮箱格式不正确");
}
}
//写个方法用来判断输入的字符串是否全是数字,利用isDigit方法去完成,
//也可以将字符串转换成字符数组,然后去判断每一个索引处值的ASCII码是否在0-9之间
public static boolean checkStrIsNum(String str){
for (int i = 0; i < str.length(); i++) {
// System.out.println(str.charAt(i));
// str.charAt(i)方法是返回指定索引处的值
if (!Character.isDigit(str.charAt(i))){//利用Character.isDigit的方法去判断是否是数字
//如果不是返回false
return false;
}
}
return true;//如果使数字就返回true
}
//写个方法用来判断邮箱中是否包含@和. 并且@再.的前面
public static boolean checkStrIsEmail(String str) {
/*这是我最先想到的方法
String str1 ="@";
String str2 =".";
//1.先判断是否有这两个符号
boolean have = str.contains(str1) && str.contains(str2);
if (have){//如果含有那两个字符则进行顺序的判断,并且肯定不能开头
if (str.indexOf(str1)<str.indexOf(str2) && str.indexOf(str1)>0){
return true;
}
}
return false;*/
//第二种方法
//先获取字符串中两个符号的索引值
int i= str.indexOf("@");//如果是字符串中没有这个字符,那么就返回-1
int j= str.indexOf(".");
if (i>0 && j>i){
return true;
}//如果是满足条件,那么返回true
//如果没有这两个字符,那么i和j都是-1,上面if判断也是错的,所以还是会返回false
return false;
}
}
13.11.3 A03:人名调序输出
题目:
编写Java程序,输入形式为:"Wang hai Yang"的人名,然后以Yang,Wang.H输出
注意:中间用,隔开,最后是.H是中间单词的大写首字母
分析:
package com.xiaowang.homWork_;
import java.util.Locale;
/**
* @Author 小王
* @DATE: 2022/4/11
*/
/*
* 编写Java程序,输入形式为:"Wang hai Yang"的人名,然后以Yang,Wang.H输出
* 注意:中间用,隔开,最后是.H是中间单词的大写首字母*/
public class HomeWork03 {
public static void main(String[] args) {
String name = "Wang hai Yang";
form(name);
}
/*
* 编写一个方法去完成:
* 1.分割字符串,split(),按照空格去分割
* 2.对分割了的String[],进行格式化 String.format()
* 3.因为最后输出的是中间数的大写首字母,所以先将其所有转成大写,然后去得到第一个索引的字母
* 3.进行校验
* */
public static void form(String str){
if (str == null) {
System.out.println("数组不能为空");
}
String[] str1=str.split(" ");
if (str1.length !=3){
System.out.println("输入格式不正确");
return;
}else
System.out.println(String.format("%s,%s.%c",str1[2],str1[0],str1[1].toUpperCase().charAt(0)));
//str1[1].toUpperCase().charAt(0)解读
//1.先将str[1]字符串全大写
//2.再去获取索引为0的字符
}
}
输出:
Yang,Wang.H
13.11.4 A04:统计个数
题目:
输入字符串,判断里面有多少个大写字母,多少个小写字母,多少个数字
分析:
package com.xiaowang.homWork_;
/**
* @Author 小王
* @DATE: 2022/4/12
*/
/*
* 输入字符串,判断里面有多少个大写字母,多少个小写字母,多少个数字*/
public class HomeWork04 {
public static void main(String[] args) {
String str = "asd1325zxASSA2121";
count(str);
}
/*思路分析:
* 1.编写一个方法去完成需求count(String str)
* 2.通过ASCII码去判断
* 3.可以直接用String方法的charAt()方法去判断
* 也可以先将其字符化char[],通过char[]去判断也行
* */
public static void count(String str){
//1.先判断是否为空
if (str ==null){
System.out.println("字符串输入有误");
return;//为空就不执行了
}
int numA=0;//统计大写
int numa=0;//统计小写
int nums=0;//统计数字
for (int i = 0; i <str.length() ; i++) {
if (str.charAt(i)>='A' && str.charAt(i)<='Z'){
numA++;
}else if (str.charAt(i)>='a' && str.charAt(i)<='z'){
numa++;
} else if (str.charAt(i) >= '1' && str.charAt(i) <= '9') {
nums++;
}
}
System.out.println("大写字母:"+numA+"\t"+"小写字母:"+numa+"\t"+"数字:"+nums);
}
}
输出:
大写字母:4 小写字母:5 数字:8
13.11.5 A05:String内部结构
分析下列代码的输出:
分析:
package com.xiaowang.homWork_;
/**
* @Author 小王
* @DATE: 2022/4/12
*/
public class HomeWork05 {
public static void main(String[] args) {
String s1="xiaowang";//池中s1为"xiaownag"
Animal a = new Animal(s1);//堆中有一个name属性去指向池中的s1,但是a是直接指向堆中name属性的
Animal b = new Animal(s1);//堆中有一个name属性去指向池中的s1,但是a是直接指向堆中name属性的
System.out.println(a==b);//false,这是判断是否是同一个对象,a和b指向不同的name对象属性
System.out.println(a.equals(b));//false,这里判断的还是a和b是否是同一个对象
//一开始可能都会觉得,equals是判断内容是否相等,那是在a和b都是String类型的变量的时候才相等,而这里:
// 因为:a和b都是Animal对象,所以调用的也是Animal对象的equals,
// 又因为Animal对象的equals方法并没有被重写,所以相当于还是判断是否是同一个对象
//底层源码:
/*
public boolean equals(Object obj) {
return (this == obj);//可以看到,返回的是父类obj的equals方法,并没有被重写,,所以比较的是对象
}*/
System.out.println(a.name == b.name);//true,a.name 和 b.name 都是指向池中s1的"xiaownag"
String s4 =new String("xiaowang");//堆中有一个value属性去指向池中的s1,但是s4 是直接指向堆中value属性的
String s5 ="xiaowang";//s5直接指向池中s1的“xaiowang”
System.out.println(s1 == s4);//false,s4指向堆中value,s1指向池中“xiaowang”,所以不等
System.out.println(s4 == s5);//false,s4指向堆中value,s5指向池中s1,所以不等
String t1 ="hello"+s1;
//字符串和对象相加的时候,是在先创建一个StringBuilder,然后相加后,返回给堆中的一个value再传给对象
String t2 ="helloxiaowang";
System.out.println(t1.intern() == t2);//true
//intern():返回字符串对象的规范表示
}
}
class Animal{
String name;
public Animal(String name){
this.name =name;
}
}
输出:
false
false
true
false
false
true
这一阶段的第一部分就到这里了,后面的不能一篇发布了,不代表完结了哦,点这里继续学习!:Java第二阶段第2部分