java详细教程

花生哦 可爱的博主

时间: 2021-07-22 阅读: 94 字数:158267

{}
来一位网友的分享

目录

  1. Java开发入门

1.1 简介

Java是人与计算机之间进行信息交流沟通的一种特殊计算机语言。

Java分为三个不同版本:

  • Java SE:Standard Edition(标准版),本课程使用的版本。
  • Java EE:Enterprise Edition(企业版)
  • Java ME:Micro Edition(嵌入式版)

2021-02-25_164949

1.2 跨平台

Java编译器将Java源程序编译成与平台无关的字节码文件(class文件),然后由Java虚拟机(JVM)对字节码文件解释执行。

不同的操作系统,安装不同的Java虚拟机,实现Java程序跨平台运行。如下,JDK15 SE版本有Linux ARM、Linux、macOS、Windows共4个平台。

<img src="Java精讲精练.assets/2021-02-25_185428.png" alt="2021-02-25_185428" style="zoom: 50%;" />

JVM(Java Virtual Machine),Java虚拟机

JRE(Java Runtime Environment),Java运行环境,包含了JVM和Java的核心类库(Java API)

JDK(Java Development Kit)称为Java开发工具,包含了JRE和开发工具

d6cb5fd98d0b20b96c8916caabd0367d

1.3 HelloWorld(必背)

三个步骤:编写源代码,编译源代码,解释执行

第一步(编写源代码):新建 新建文本文档.txt,修改(含后缀txt)为HelloWorld.java,输入:

//public class后面代表定义一个类的名称,类是Java当中所有源代码的基本组织单位。
public class HelloWorld {//第三个单词必须和源文件名称HelloWorld完全一样,大小写也要一样!!!
    public static void main(String[] args) {//这行的内容是万年不变的固定写法,代表main方法。表示程序执行的起点。
        System.out.println("Hello, World!");//打印输出语句,即屏幕显示。
    }
}

第二步(编译源代码):HelloWorld.java移动到JDK的bin目录,在资源管理器的地址栏复制bin目录的完整路径:

image-20210406122400254

win +r输入cmd,输入:cd +空格 + 粘贴的目录:

image-20210406122548161

编译命令:javac HelloWorld.java

image-20210406165616070

执行后可以看到生成了一个HelloWorld.class跨平台文件

image-20210406165744569

第三步(解释执行):执行HelloWorld.class,使用命令:java HelloWorld:(注意不带、不带、不带文件后缀.class)

image-20210406172012000

1.4 IDE工具

IDE是集成开发环境:Integrated Development Environment的缩写。使用IDE的好处在于,可以把编写代码、组织项目、编译、运行、调试等放到一个环境中运行,能极大地提高开发效率。

IntelliJ Idea是由JetBrains公司开发的一个功能强大的IDE,分为免费版和商用付费版。JetBrains公司的IDE平台也是基于IDE平台+语言插件的模式,支持java开发环境、Python开发环境、Ruby开发环境、PHP开发环境等,这些开发环境也分为免费版和付费版。

1.5 IDEA的项目结构

<img src="Java精讲精练.assets/2021-02-25_212832.png" alt="2021-02-25_212832" />

所谓包,就是文件夹,用来放置源文件(.java文件)。

1.6 IDEA基本使用

程序解压后的目录 D:\ideaIU_2020.1.1 ,请确认如下,自带8、15两个JDK,均为64位。

第一次运行双击IDEALauncher.exe打开软件。

<img src="Java精讲精练.assets/2021-02-25_234530.png" alt="2021-02-25_234530" />

第二次后可以直接双击D:\ideaIU_2020.1.1\bin目录的idea64.exe打开软件。

image-20210406173812695

第一次运行提示如下:

<img src="Java精讲精练.assets/2021-02-25_102716.jpg" alt="2021-02-25_102716" />

<img src="Java精讲精练.assets/2021-02-25_102724.jpg" alt="2021-02-25_102724" />

1.7 IDEA编写HelloWorld(必会)

第一步:点击Create New Project 新建项目

<img src="Java精讲精练.assets/2021-02-26_000102.png" alt="2021-02-26_000102" />

第二步:点击Empty Project 空项目

<img src="Java精讲精练.assets/2021-02-26_000207.png" alt="2021-02-26_000207" />

第三步:填写项目名称;保存路径,老师默认配置了D:\ideaIU_2020.1.1\code目录:

<img src="Java精讲精练.assets/2021-02-26_000308.png" alt="2021-02-26_000308" style="zoom:;" />

在弹出的project structure窗口中点击New Module 新建模块

<img src="Java精讲精练.assets/2021-02-25_212545.png" alt="2021-02-25_212545" />

如果未弹出project structure窗口,则在软件菜单的 File->Project structure中打开。

c344efec-a8f2-4a70-8131-497ced226336

第四步:选择Module SDK ,本次课程都选JDK 15版本

<img src="Java精讲精练.assets/2021-02-26_000840.png" alt="2021-02-26_000840" />

第五步:给模块命名;存储路径按自动提示的包含在项目内。

<img src="Java精讲精练.assets/2021-02-26_001048.png" alt="2021-02-26_001048" />

第六步:默认

<img src="Java精讲精练.assets/6ab987ae-0ff2-4863-bb83-8f793cf02664.png" alt="6ab987ae-0ff2-4863-bb83-8f793cf02664" />

完成如下:

2021-02-26_001254

第七步:在src右键,新建Java class文件

2021-04-06_182333

第八步:输入:类名称 HelloWorld

2021-04-06_182114

前八步总结:

2

第九步:编写源代码并运行

  • 输入:m ,待main方法提示出来后按回车Tab键,自动生main方法;
  • 输入:sout 自动生成打印输出语句;
  • 输入:Hello, World!
  • Run 'HelloWorld.main()'

1

1.8 IDEA项目关闭

3

2. Java基础语法

2.1 注释、关键字、标识符、常量

// 这是单行注释。

===============================================================================

/*
这是多行注释;
这是多行注释。
*/

===============================================================================

//关键字是java JDK自定义的,全部小写字母。
public class   static void
    
===============================================================================

//标识符是自己定义的,如:类名称、方法名称、变量名称等。
/*标识符可以包含:英文字母26个(区分大小写) 、 0-9数字、$(美元符号)和_(下划线)。标识符不能以数字开头,不能是关键字。
命名规范:
类名称:每个单词首字母大写,如:HelloWorld。(大驼峰式)
方法名称:首字母小写,后面每个单词首字母大写,如:myFunction。(小驼峰式)
变量名称:全部小写,如:name。
*/
    
===============================================================================
    
//常量,在java运行中固定不变。
public class ConsDemo {
    public static void main(String[] args){
        //整型常量,直接写整数(十进制、八进制,0开头、十六进制,0x开头),例如:-100、0、100
        System.out.println(168);//168
        
        //实型常量(单双精),直接写小数,例如:-1.2F、0.0、3.6
        System.out.println(0.666);//0.666
        
        //字符常量,用单引号括起来的单个字符(不能2个)、转义字符。例如:'中'、'A'、'b'、'\n'
        System.out.println('A');//A
        System.out.println('\n'); //换行
        //System.out.println(''); 字符常量不能是空,会报错;注意如果输出空格也是一个字符,不会报错。
        
        //字符串常量,凡是用双引号引起来的,都叫做字符串常量。例如:"abc"、"a"、"你好!"
        System.out.println("你好!Java");
        System.out.println(""); //字符串常量可以是空的。
        
        //布尔常量  true、false
        System.out.println(true);
        System.out.println(false);
        
        //null空常量,不能直接打印输出
        //System.out.println(null); 报错
    }
}

2.2 数据类型

四大类,八种基本数据类型:

  • 整数型:byte、short、int、long
  • 浮点型:float、double
  • 字符型:char
  • 布尔型:boolean

byte:字节型,-128~127

short:短整型,-32768~32767

int:整型,约正负21亿

long:长整型

float:单精度浮点型

double:双精度浮点型

char:字符型

boolean:布尔型

Java中的默认类型:整数类型是 int 、浮点类型是 double long类型:数据后加L表示。 float类型:数据后加F表示。

2.3 变量

2.3.1 变量格式

//变量分两步来定义
数据类型 变量名;//创建一个变量
变量名 = 数据值;//将右边的值,赋值给左边的变量
//或者一步到位定义
数据类型 变量名 = 数据值;
========================================================
int num1;
num1 = 100;
========================================================
int num2 = 200; //推荐这种方法
========================================================
//可以通过一个语句来创建多个变量,但是一般情况不推荐这么写。
int a,b,c;
//先创建,再各自分别赋值
a = 10;
b = 20;
c = 30;
========================================================
//同时创建三个int变量,并且同时各自赋值,不推荐。
int x = 10,y = 20, z = 30;
========================================================
//将一个变量的数据赋值给另外一个变量
int num3 = 56;
int num4 = num3;
System.out.println(num4); //56

2.3.2 定义各种变量

public class VarDemo {
    public static void main(String[] args){
        //定义字节型变量
        byte b = 127; //注意:右侧数值的范围不能超过左侧数据类型的取值范围
        System.out.println(b);//127
        
        //定义短整型变量
        short s = 10000;
        System.out.println(s);//10000
        
        //定义整型变量
        int i = 123456;
        System.out.println(i);//123456
        
        //定义长整型变量
        long l = 12345678900L; //注意带L
        System.out.println(l);//12345678900
        
        //定义单精度浮点型变量
        float f = 5.5F; //注意带F
        System.out.println(f);//5.5
        
        //定义双精度浮点型变量
        double d = 8.5;
        System.out.println(d);//8.5
        
        //定义字符型变量
        char c = 'A';
        System.out.println(c);//A
        
        //定义布尔型变量,true、false
        boolean bool = true;
        System.out.println(bool);//true
    }
}       

2.3.3 变量的作用域

//作用域理解,从定义变量的一行开始,一直到直接所属的大括号结束为止。
{
    int num4 = 6;
    System.out.println(num4);//6
}
    System.out.println(num4);//这句没有用,打印不出来,报错。
====================================================================
//既然有作用域限制,那么我们可不可以在不同的大括号,定义一个相同的变量名呢?
{
    int num5 = 80;  
    System.out.println(num5);//80
}
    int num5 = 90;
    System.out.println(num5);//90
    //如上,可以这么写,但不推荐

2.4 自动转换(隐式)

把一个数据范围小数值或者变量赋值给另一个数据范围大变量,会出现数据类型的自动转换现象。这种转换方式是自动的。 不需要你写代码实现。

字节大小

示例1:

public class LowToHigh {
    public static void main(String[] args) {
        //100默认是int类型,自动转换为double类型
        double num1 = 100; 
        System.out.println(num1); // 输出100.0

        //200默认是int类型,自动转换为long类型
        long num2 = 200;
        System.out.println(num2); // 输出200
        
        //300.0默认是float类型,自动转换为double类型
        double num3 = 300F;
        System.out.println(num3); // 输出300.0
        
        //400是long类型,自动转换为float类型
        float num4 = 400L;
        System.out.println(num4); // 输出400.0
    }
}

示例2:

//int 和 double运算,int自动提升为double
public class LowToHigh2 {
    public static void main(String[] args) {
        //e = i + d = 3.0 + 2.5 = 5.5
        int i = 3;
        double d = 2.5;
        double e = i+d; //注意这里的double
        //int z = i+d; 提示: 不兼容的类型: 从double转换到int可能会有损失
        System.out.println(e);
    }
}

示例3

//byte、short和char类型数据参与运算均会自动转换为int类型。
public class ByteToHigh {
    public static void main(String[] args) {
        byte b1 = 10;
        byte b2 = 20;
        int num = b1 + b2; //注意int
        System.out.println(num);//30
        
        // byte b3 = b1 + b2; 提示:从int转换到byte可能会有损失
        //可修改为:
        byte b3 = (byte) (b1 + b2);  //强制转换
        System.out.println(b3);//30
    }
}

示例4

//char类型是查找编码表中对应的int值进行计算
public class CharToInt {
    public static void main(String[] args) {
        int a = 'a'; //将'a'的对应97赋值给int
        System.out.println(a); // 将输出97
        int i = 100;
        System.out.println(a+i);  // 将输出197
        
        //理解
        char chr = '0';
        System.out.println(chr);//输出0字符
        System.out.println(chr+0);//48
        
        //汉字转int
        int b = '啊';//左侧是int类型,右侧是char类型,发生自动类型转换
        System.out.println(b); // 将输出21834
    }
}

boolean类型不能与其他基本数据类型相互转换。

2.5 强制转换(显式)

强制类型转换是将 取值范围大的类型 强制转换成 取值范围小的类型 。

强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出。

需要你写代码实现。

范围小的类型 范围小的变量名 = (范围小的类型)范围大的数据;
====================================================
int i = (int) 100.8;//100

示例1

public class HighToLow {
    public static void main(String[] args) {
        // double类型数据强制转成int、byte类型,直接去掉小数点,精度损失。
        int i = (int) 250.8;
        byte b = (byte) 101.86;
        System.out.println(i);//250
        System.out.println(b);//101
        
        //long、float强制转换为int
        int num1 = (int) 102L;
        System.out.println(num1);//102
        int num2 = (int) 103.8F;
        System.out.println(num2);//103
        
        //把超过21亿的数给int,数据溢出。(带8个0为1亿)  
        int num3 = (int) 6000000000L;
        System.out.println(num3);//1705032704,大杯往小杯倒水,数据丢失
    }
}

2.6 算数运算符

示例1

public class SuanShu {
    public static void main(String[] args) {
        double a = 10;
        double b = 3;
        //加法运算
        System.out.println(a + b); // 13.0
        
        //减法运算
        System.out.println(a - b); // 7.0
        
        //乘法运算
        System.out.println(a * b); // 30.0
        
        //除法运算
        System.out.println(a / b); // 3.3333333333333335
        
        //取模(余)运算
        System.out.println(a % b); // 输出结果1.0
    }
}

示例2

//自增++  自减--
//单独使用,前++和后++没有区别!
int i = 10;
i++; // 单独使用
System.out.println(i); // 11
int j = 10;
++j; // 单独使用
System.out.println(j); // 11

============================================================
//混合使用,因java程序是自左至右执行的,先碰着谁,就取谁的值
//++在前
int num1 = 10;
System.out.println(++num1);//11,碰着++,立刻+1,立刻取+1值
System.out.println(num1);//11
//++在后
int num2 = 20;
System.out.println(num2++);//20  先碰着变量,立刻取变量原来的值
System.out.println(num2);//21  上一条语句后,变量自加1了
===============================================================

//综上,理解:
//前++
int a = 10;
int b = ++a;
System.out.println(b); // 11
System.out.println(a); // 11

//后++
int x = 10;
int y = x++;
System.out.println(y);//10
System.out.println(x);//11

小结

++a,变量立刻 +1,立刻拿着+1后的值进行使用。 a++,首先使用变量原本的值,然后才让变量+1。

字符串的+操作:"abc"+"d"的结果是"abcd"任何数据类型和字符串进行连接的时候,结果都会变成字符串。例如:System.out.println("abc"+10+20);//abc1020

2.7 赋值运算符

赋值运算符,就是将符号右边的值,赋给左边的变量

public class FuZhi {
    public static void main(String[] args) {
        //赋值
        int a = 5;
        
        //加后赋值
        a += 5;
        System.out.println(a);//a=a+5=5+5=10 变量a先加5,再赋值变量a
        
        //减后赋值
        a -= 5;
        System.out.println(a);//a=a-5=10-5=5
        
        //乘后赋值
        a *= 5;
        System.out.println(a);//a=a*5=5*5=25
        
        //除后赋值
        a /= 5;
        System.out.println(a);//d=d/5=25/5=5
        
        //取余后赋值
        a %= 5;
        System.out.println(a);//e=e%5=0
    }
}
//扩展的赋值运算符自带了强制类型转换。
public class FuZhi2 {
    public static void main(String[] args) {
        short s = 10;
        s += 10; // 此行代码没有问题,隐含了强制类型转换,相当于 s = (short) (s + 10);
        System.out.println(s);//s计算后等于20
        //s = s + 10; // 运算中s提升为int类型,提示:不兼容的类型: 从int转换到short可能会有损失。
    }
}

2.8 比较运算符(比大小、比是否一样)

比较运算符的结果都是boolean类型,要么是true,要么是false。

public class BiJiao {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        //等于
        System.out.println(a == b); // false
        
        //不等于
        System.out.println(a != b); // true
        
        //大于
        System.out.println(a > b); // false
        
        //大于等于
        System.out.println(a >= b); // false
        
        //小于
        System.out.println(a < b); // true
        
        //小于等于
        System.out.println(a <= b); // true
        
        //运算结果赋值给boolean类型的变量
        boolean flag = a > b;
        System.out.println(flag); // false
    }
}

2.9 逻辑运算符

逻辑运算符是用来连接两个布尔类型结果的运算符,运算结果都是布尔值 true 或者 false

数学当中可以这样写:1<x<3;但程序当中不允许这样写,需要用到逻辑运算符:(x > 1) & (x < 3);

public class LuoJi {
    public static void main(String[] args) {
        int i = 10;
        int j = 20;
        int k = 30;

        //& “逻辑与”,并且的关系,真真为真,其余为假。
        System.out.println((i > j) & (i > k)); //false & false,输出false
        System.out.println((i < j) & (i > k)); //true & false,输出false
        System.out.println((i > j) & (i < k)); //false & true,输出false
        System.out.println((i < j) & (i < k)); //true & true,输出true
        System.out.println("--------");

        //| “逻辑或”,或者的关系,有一真为真。
        System.out.println((i > j) | (i > k)); //false | false,输出false
        System.out.println((i < j) | (i > k)); //true | false,输出true
        System.out.println((i > j) | (i < k)); //false | true,输出true
        System.out.println((i < j) | (i < k)); //true | true,输出true
        System.out.println("--------");

        //^ “逻辑异或”,相同为假,不同为真(一真一假才为真)。
        System.out.println((i > j) ^ (i > k)); //false ^ false,输出false
        System.out.println((i < j) ^ (i > k)); //true ^ false,输出true
        System.out.println((i > j) ^ (i < k)); //false ^ true,输出true
        System.out.println((i < j) ^ (i < k)); //true ^ true,输出false
        System.out.println("--------");

        //! “逻辑非”,取反
        System.out.println((i > j)); //false
        System.out.println(!(i > j)); //!false,输出true
    }
}

2.10 短路逻辑运算符

  • 逻辑与&,无论左边真假,右边都要执行。

  • 短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。

  • 逻辑或|,无论左边真假,右边都要执行。

  • 短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。

public class DuanLu {
    public static void main(String[] args) {
        int x = 3;
        int y = 4;
        System.out.println((x++ > 4) & (y++ > 5)); // 两个表达都会运算
        System.out.println(x); // 4
        System.out.println(y); // 5
        int a = 3;
        int b = 4;
        System.out.println((a++ > 4) && (b++ > 5)); // 左边得出结果为false,右边不参与运算
        System.out.println(a); // 4
        System.out.println(b); // 4
    }
}

2.11 三元运算符

数据类型 变量名称 = 条件判断 ? 表达式1 : 表达式2;
=================================================
//条件判断为真,执行表达式1,为假执行表达式2
int a = 3 > 4 ? 3 : 4;//正确,a = 4

int a = 3 > 4 ? 3.4 : 4;//错误,数据类型不一致

System.out.println(3 > 4 ? 3 : 4);//这种直接打印的方法正确

3 > 4 ? 3 : 4;//错误,没有被使用。提示:不是语句
public class San {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a > b ? a : b; // 判断 a>b 是否为真,如果为真取a的值,如果为假,取b的值
        System.out.println(c); //20
    }
}

2.12 运算符优先级

实际编写代码时,尽量使用括号()实现想要的运算顺序,以免产生歧义。

3. 流程控制语句

3.1 顺序结构

public static void main(String[] args){
//顺序执行,根据编写的顺序,从上到下运行
    System.out.println(1);
    System.out.println(2);
    System.out.println(3);
}

3.2 判断结构之if语句

3.2.1 if语句格式1

//单if语句,只有一种情况可执行
if (关系表达式) {
    语句体;
}
//或者如下,不建议!!!
if (关系表达式) 语句体;
====================================================================
//如果关系表式为真,则执行语句体;为假则不执行。
int a = 10;
int b = 20;
if (a == b) {//布尔表达式
    System.out.println("a等于b"); //a等于b
}
System.out.println("a不等于b"); //想再加一种情况,只能顺序写代码,与if无关。

3.2.2 if语句格式2

//标准if语句,必须二选一执行
if (关系表达式) {
    语句体1;    
} else {
    语句体2;    
}
//或者如下,不建议!!!
if (关系表达式) 
    语句体1;    
 else 
    语句体2;    
=============================================================
//如果关系表式为真,则执行语句体1;为假则执行语句体2。
int a = 10;
int b = 20;
if (a > b) {
    System.out.println("a的值大于b");
} else {
    System.out.println("a的值不大于b"); //a的值不大于b
}

3.2.3 if语句格式3

//复合、扩展的if; else负责收尾。
if (关系表达式1) {
    语句体1;//如果执行了这步,下面的都不会再执行。
} else if (关系表达式2) {
    语句体2;//如果执行了这步,下面的也都不会再执行,以此类推。
} 
…
} else if (关系表达式n) {
    语句体n;    
} else {
    语句体n+1;
}

//或者如下,不建议!!!
if (关系表达式1) 
    语句体1;//如果执行了这步,下面的都不会再执行。
 else if (关系表达式2) 
    语句体2;    
…
else if (关系表达式n) 
    语句体n;    
 else 
    语句体n+1;
=========================================================================================
if 的关系表式为真,则执行它的语句体,为假则往下走,判断 else if ,以此类推;最后所以都不满足则执行 else 

示例:键盘录入一个星期数(1,2,...7),输出对应的星期一,星期二,...星期日

import java.util.Scanner;//导包
public class IfDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);//创建对象
        System.out.println("请输入一个星期数(1-7):");
        int week = sc.nextInt();//接收数据
        if (week == 1) {
            System.out.println("星期一");
        } else if (week == 2) {
            System.out.println("星期二");
        } else if (week == 3) {
            System.out.println("星期三");
        } else if (week == 4) {
            System.out.println("星期四");
        } else if (week == 5) {
            System.out.println("星期五");
        } else if (week == 6) {
            System.out.println("星期六");
        } else {
            System.out.println("星期日");
        }
    }
}

3.2.4 if语句和三元运算符的互换

//定义变量,取a和b的较大值
int a = 10;
int b = 20;
int c;
if (a > b) {
    c = a;
} else {
    c = b;
}
//上述功能可以改写为三元运算符形式
c = a > b ? a : b;

3.2.5 三个if格式快速输入

if格式1,模板名称ifm;布尔表达式为一般为比较运算。

//初始化语句
if ($END$) { //布尔表达式
    
}

if格式2,模板名称ifelse

//初始化语句
if ($END$) { //布尔表达式
    
} else {
    
}

if格式3,模板名称可设置为eif

//初始化语句
if ($END$) { //布尔表达式
    
} else if () { //布尔表达式
    
} else if () { // Ctrl+D复制多个
    
} else {
    
}

3.3 判断结构之switch语句

3.3.1 switch格式

//case值每个必须不一样,语句里顺序可以颠倒。
switch (表达式) {
    case 常量值1:
        语句体1;
        break;
    case 常量值2:
        语句体2;
        break;
...
    default:
        语句体n + 1;
        break;//可以省略,但是不建议
}
=======================================================================
计算表达式的值,依次和 case 比较,一旦对应就执行,并通过 break 跳出,结束;所有都不满足则执行 default

示例:

import java.util.Scanner;//导包
public class SwitchDemo01 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);//创建对象
        System.out.println("请输入一个星期数(1-7):");
        int week = sc.nextInt();//接收数据
//switch语句实现选择
        switch (week) {
            case 1:
                System.out.println("星期一");
                break;
            case 2:
                System.out.println("星期二");
                break;
            case 3:
                System.out.println("星期三");
                break;
            case 4:
                System.out.println("星期四");
                break;
            case 5:
                System.out.println("星期五");
                break;
            case 6:
                System.out.println("星期六"); //星期六
                break;
            case 7:
                System.out.println("星期日");
                break;
            default://测试把default放在第5位,输入8看结果(顺序测试)
                System.out.println("你输入的数字有误");
                break;
        }
    }
}
/* 穿透现象:在switch中,如果case不写break,就不会判断下一个case的值,直接向后运行,直到遇到break,或者整体switch结束。
*/
public class SwitchDemo02 {
    public static void main(String[] args) {
        int i = 5;
        switch (i) {
            case 0:
                System.out.println("执行case0");
                break;
            case 5:
                System.out.println("执行case5");
                //break; 这步如果没有break,则会继续向下执行case 6,不会跳出。
            case 6:
                System.out.println("执行case6");
                //break; 如上,case 5没有break,则这步会被执行,导致case 6被执行;
                //如果这步还是没有break,则default也会被执行。
            default:
                System.out.println("执行default");
               //break;这里有没有break都无所谓了,因为代码都要结束了。
        }
    }
}

3.3.2 switch 快速输入

自定义代码块,模板名称定义为sw

//初始化语句
switch ($END$) { //表达式,只能返回byte,short,char,int,enum枚举,String字符串 类型的值。
    case : //常量
        
        break;
    case :
        
        break;
    default:
        
        break;
}

3.4 循环结构之for语句

3.4.1 for格式

/*
循环基本组成:
初始化表达式:在循环最初执行,而且只做一次。
布尔表达式(条件判断):如果成立,则循环继续;如果不成立,则循环退出。
循环体:重复要做的事情,若干行语句。
步进:每次循环之后都要进行的扫尾工作。
*/
for (初始化表达式①;布尔表达式②;步进表达式④) {
    循环体③
}
=====================================================
//输出数据1~5
for(int i=1; i<=5; i++) {//这行定义循环几次
    System.out.println(i);
    System.out.println("第"+i+"次循环");
}
=====================================================
//输出数据5~1
for(int i=5; i>=1; i--) {
    System.out.println(i);
}

**执行流程 **

  • 执行顺序:①②③④>②③④>②③④…②不满足为止。
  • ①负责完成循环变量初始化
  • ②负责判断是否满足循环条件,不满足则跳出循环
  • ③具体执行的语句
  • ④循环后,循环条件涉及变量的变化情况

3.4.2 for快速输入

输入fori按Tab

3.5 循环结构之while语句

3.5.1 while格式

初始化表达式①;
while (布尔表达式②) {
    循环体③;
    步进表达式④;
}
===============================================================
//输出数据1~5
int j = 1;
while (j <= 5) {
    System.out.println(j);
    j++;
}
//增加
System.out.println(j);//j等于几?
===============================================================
//理解:跟for对应关系
for(int i=1; i<=5; i++) {
    System.out.println(i);    
}

**执行流程 **

  • 执行顺序:①②③④>②③④>②③④…②不满足为止。
  • ①负责完成循环变量初始化。
  • ②负责判断(比较运算)是否满足循环条件,不满足则跳出循环。
  • ③具体执行的语句。
  • ④循环后,循环变量的变化情况。

3.5.2 while快速输入

自定义代码,模板名称定义为wh

//初始化语句
while ($END$){ //布尔表达式
//循环体
//步进表达式
}

3.6 循环结构之do while语句

3.6.1 do while格式

初始化表达式①
do{
    循环体③
    步进表达式④
}while(布尔表达式②);
==============================================
//输出数据1~5
int k = 1;
do {
    System.out.println(k);
    k++;
} while (k <= 5);
==============================================
//理解:以下写法正确吗,循环走了几遍?
int k = 2;
do {
    System.out.println(k);
    k++;
} while (k <= 1);

**执行流程 **

  • 执行顺序:①③④>②③④>②③④…②不满足为止。
  • ①负责完成循环变量初始化。
  • ②负责判断(比较运算)是否满足循环条件,不满足则跳出循环。
  • ③具体执行的语句
  • ④循环后,循环变量的变化情况

3.6.2 do while快速输入

自定义代码,模板名称定义为dw

//初始化语句
do {
    //循环体
    //步进表达式
} while ($END$); //布尔表达式

3.6.3 死循环(无限循环)

循环中的条件永远为true,死循环的是永不结束的循环。 一般是没有步进语句,循环结束不了。

  1. for(;;){} for(int i =1;i<=10; ){}
  2. while(true){}
  3. do {} while(true);

示例1:for(;;){} 没有任何判断,永远为true,无限循环。

        for(;;){
            System.out.println("我喜欢学习Java!");
        }

示例2:while(true){} 因无步进表达式,布尔表达式永远为true,导致循环体无限执行。

        int i = 1;//初始化语句
        while (i==1) { //布尔表达式
            System.out.println("我喜欢学习Java!");//循环体
            //步进表达式
        }

示例3:do {} while(true); 同理

        int i = 1;//初始化语句
        do {
            System.out.println("我喜欢学习Java!");//循环体
            //步进表达式
        } while (i==1); //布尔表达式

注意:在死循环下面放代码会提示错误: 无法访问的语句。

3.7 三种循环分析

  • 如果条件判断从来没有满足过,那么for和while执行次数为0,但是do while执行了一次。
  • for循环的变量只能在for循环内部使用。while和do while因为初始化语句在外面,循环结束后还可继续使用。
  • 建议:循环次数确定的场景多用for循环(因为代码少),循环次数未知的多用while循环,do while用得少一些。

3.8 跳转语句

3.8.1 break

**使用场景:终止switch,或者循环 **

  • 在选择结构switch语句中
  • 在循环语句中
  • 离开使用场景的存在是没有意义的

示例:不加break时,for可执行10次(可自行验证);加上break后,只能执行到第2次。

public class BreakDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
        //需求:打印完两次HelloWorld之后结束循环
            if (i == 3) {
                break;//直接跳出整个循环
            }
            System.out.println("HelloWorld第" + i + "次");
        }
    }
}

3.8.2 continue

**使用场景:结束本次循环,继续下一次的循环 **

示例:不加continue时,for可执行10次(可自行验证);加上continue后,不打印第3次。

public class ContinueDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
        //需求:不打印第三次HelloWorld
            if (i == 3) {
                continue;//结束本次循环,不打印第3次,继续下一次循环
            }
            System.out.println("HelloWorld第" + i + "次");
        }
    }
}

3.9 循环嵌套

嵌套循环,是指一个循环的循环体是另一个循环。比如for循环里面还有一个for循环,就是嵌套循环。

*总循环次数=外循环次数内循环次数 **

for(初始化表达式①; 循环条件②; 步进表达式⑦) {
    for(初始化表达式③; 循环条件④; 步进表达式⑥) {
        执行语句⑤;
    }
}
==================================================================
//外循环控制小时的范围,内循环控制分钟的范围
for (int hour = 0; hour < 24; hour++) {//外层控制小时
    for (int minute = 0; minute < 60; minute++) {//内层控制分钟
        System.out.println(hour + "时" + minute + "分");
    }
    System.out.println("--------");
}
===================================================================
0时0分
0时1分
...
0时59分
--------
...
--------
23时0分
...
23时59分
--------

嵌套循环执行流程:

  • 执行顺序:①②③④⑤⑥>④⑤⑥>⑦②③④⑤⑥>④⑤⑥
  • 外循环一次,内循环多次。
  • 理解:

    • 请反复理解这句话(整个内循环,就是外循环的一个循环体,内部循环体没有执行完毕,外循环是不会继续向下执行的)
  • 结论:

    • 外循环执行一次,内循环执行一遍。

4. 数组

容器:是将多个数据存储到一起,每个数据称为该容器的元素。

生活中的容器:水杯,食堂,教室。

数组就是存储数据长度固定的容器,且多个数据的数据类型是一致的。 数据类型含八大类和引用数据类型。数组长度一旦指定,不可更改。

数组也是一种引用数据类型。

4.1 格式1 静态初始化

静态初始化,给数组指定内容。

数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
================================================================
int[] arrayA = new int[]{1,2,3,4,5};
//可以拆分成两个步骤:
int[] arrayA;
arrayA = new int[]{1,2,3,4,5};

4.2 格式2 省略的静态初始化

数据类型[] 数组名 = {元素1,元素2,元素3...};
========================================================
int[] arrayA = {1,2,3,4,5};//不可拆分

4.3 格式3 动态初始化

动态初始化指定长度。

数组存储的数据类型[] 数组名字 = new 数组存储的数据类型[长度];
============================================================
int[] arrayB = new int[5];
//可以拆分成两个步骤:
int[] arrayB;
arrayB = new int[5];

如果不确定数组内容用动态初始化,确定内容用静态初始化。

4.4 数组快速输入

静态初始化,arj

$VAR1$[] $VAR2$ = {$VAR3$};$END$

静态初始化,arj2

$VAR1$[] $VAR2$ = new $VAR3${$VAR4$};$END$

动态初始化,ard

$VAR1$[] $VAR2$ = new $VAR3$;$END$

4.5 数组的访问

每一个存储到数组的元素,都自动拥有一个编号,从0号开始,这个自动编号称为数组索引(index),通过**数组名[索引];**访问数组中的元素。

数组的长度通过**数组名.length ** 取得,因此数组的最大索引值为**数组名.length-1 ** 。

public class ArrayDemo {
    public static void main(String[] args) {
        //数组静态初始化的省略格式
        int[] arr = {10, 20, 30};
        
        //打印数组名称,得到的是数组对应的内存地址哈希值
        System.out.println(arr);
        
        //打印数组中的元素
        System.out.println(arr[0]);//第0号元素为10
        System.out.println(arr[1]);//第1号元素为20
        System.out.println(arr[2]);//第2号元素为30
        
        //可以将数组当中的某个元素赋值给变量
        int i=arr[1];
        System.out.println(i);//20
        
        //为0索引元素赋值为6
        arr[0] = 6;
        System.out.println(arr[0]);//6
        
        //打印数组的长度,输出结果是3
        System.out.println(arr.length);
    }
}

4.6 动态数组默认值

如果是整数类型,那么默认为0; 如果是浮点类型,那么默认为0.0; 如果是字符类型,那么默认为'\u0000';u是unicode,后面4个0是16进制;是不可见字符,并不是空格。 如果是布尔类型,那么默认为false; 如果是引用类型,那么默认为null。 字符串是引用类型,其它涉及到面向对象知识。

public class HelloWorld {
    public static void main(String[] args) {
        //整数类型
        int[] arrayA = new int[2];
        System.out.println(arrayA[0]);//0
        System.out.println(arrayA[1]);//0

        //浮点类型
        double[] arrayB = new double[2];
        System.out.println(arrayB[0]);//0.0
        System.out.println(arrayB[1]);//0.0

        //字符类型
        char[] arrayC = new char[2];
        System.out.println(arrayC[0]);//不可见
        System.out.println(arrayC[1]);//不可见

        //布尔类型
        boolean[] arrayD = new boolean[2];
        System.out.println(arrayD[0]);//false
        System.out.println(arrayD[1]);//false

        //引用类型
        String[] arrayE = new String[2];
        System.out.println(arrayE[0]);//null
        System.out.println(arrayE[1]);//null
    }
}

4.6 Java内存(理解)

内存是计算机中的重要原件,临时存储区域,作用是运行程序。

编写的程序存放在硬盘中,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。 Java虚拟机要运行程序,必须要向操作系统申请“一块”内存,并对内存进行空间的分配和管理。

Java把申请过来的内存划分为5个部分:

  • 栈(Stack,线性表):存放的都是方法中的局部变量。方法的运行一定要在栈当中。

    • 局部变量:方法的参数,或者是方法{}内部的变量。
    • 作用域:一旦超出作用域,立刻从栈内存当中消失。
  • 堆(Heap):凡是new出来的东西,都在堆当中。所以数组是在堆中存放。

    • 堆内存里面的数据都有一个地址值:16进制
    • 堆内存里面的数据,都有默认值。规则:
      • 整数类型,默认为0
      • 浮点类型,默认为0.0
      • 字符类型,默认为'\u0000'
      • 布尔类型,默认为false
      • 引用类型,默认为null
  • 方法区(Method Area):存储.calss相关信息,包含方法的信息(含main、自定义方法)。注意是.class内的都存储,但只是存了方法的死信息,方法的运行一定要在栈当中。这个动作叫做进栈。

  • 本地方法栈(Native Method Stack):与操作系统相关,与开发无关。

  • 寄存器(PC Register):与CPU相关,与开发无关。

4.6.1 一个数组内存图

2021-04-19_232524

4.6.2 两个数组内存图

互不相关模型

2021-04-20_112316

4.6.3 指向同一个数组

把一个数组的地址值赋值给另一个数组,叫做引用。其实就是操作同一个数组。

2021-04-20_114249

4.7 数组常见操作

4.7.1 数组越界异常

int[] arr = {1,2,3};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
========================================================
运行结果:
//不存在3号元素,发生越界异常。看见了能懂是什么意思...
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    at ChengJi.main(Demo.java:9)
1
2
3

4.7.2 数组空指针异常

引用类型(含数组)可以赋一个null值,表示什么都没有。

int[] arr = null;
System.out.println(arr[0]);

//运行结果:空指针异常
Exception in thread "main" java.lang.NullPointerException: Cannot load from int array because "arr" is null
    at ChengJi.main(Demo.java:6)

==========================================================
//先赋值,后赋null也是空指针异常
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);

4.7.3 数组名称相同

数组一旦创建,在程序运行期间,长度不可改变。

public class Demo {
    public static void main(String[] args) {
        int[] arrayC = new int[3];
        System.out.println(arrayC);//地址值1
        System.out.println(arrayC.length);//3

        arrayC = new int[5];
        System.out.println(arrayC);//地址值2
        System.out.println(arrayC.length);//5

    }
}

如上,既然数组一旦创建长度不可变,但是为什么上面代码的数组长度会不一样。

2021-04-20_145201

4.7.4 数组遍历

将数组中的每个元素分别获取出来,就是数组遍历。

int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);

如果数组元素非常多,我们就得用循环来输出。

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

4.7.5 获取数组最大值

public static void main(String[] args) {
    int[] arr = { 5, 15, 2000, 10000, 100, 4000 };
    //定义变量max用于记住最大数,先假设第0号元素为最大值
    int max = arr[0];
    //遍历数组,取出每个元素
    for (int i = 1; i < arr.length; i++) {//初始从[1]开始
        if (arr[i] > max) {//比较arr[i]是否大于max
            max = arr[i];//如果条件判断为true,就将arr[i]保存到max
        }
    } 
    System.out.println("数组最大值是: " + max);
}
//求最小值

4.7.6 数组的元素反转

数组元素反转,就是对称位置的元素交换。比如:1和5交换,2和4交换。需要两个编号进行交换。

image-20210420152400430

如下图,交换原理分析(假如条件判断为: <= ,可以吗?):无标题

public static void main(String[] args) {
    int[] arr = { 1, 2, 3, 4, 5 };
    //初始表达式中,left是数组最小编号,right是数组最大编号
    //判断循环次数:第1次是left=0,right=4;第2次是left=1,right=3
    for (int left = 0, right = arr.length - 1; left < right; left++, right--){
        //利用第三方变量完成数组中的元素交换。
        //第1次循环:完成arr[0]与arr[4]的交换;
        //第2次循环:完成arr[1]与arr[3]交换;
           int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
    // 输出反转后的数组
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}

4.7.7 数组的值排序

常用冒泡排序法:不断比较数组中相邻的两个元素,较小者向上浮,较大都向下沉,与汽泡上浮相似。

public class Demo {
    public static void main(String[] args) {
        int[] arr = { 6, 9, 13, 1, 5 };
        for (int i = 0; i <arr.length-1 ; i++) {//外循环,循环次数为arr.length-1
            for (int j = 0; j < arr.length - i - 1; j++) {//内循环
                /*
                  第1轮,arr.length - i - 1 = 5-0-1=4:
                    第1次循环:arr[0]与arr[1]比较,不管怎么样,arr[1]结果是最大值
                    第2次循环:arr[1]与arr[2]比较,不管怎么样,arr[2]结果是最大值
                    第3次循环:arr[2]与arr[3]比较,不管怎么样,arr[3]结果是最大值
                    第4次循环:arr[3]与arr[4]比较,不管怎么样,arr[4]结果是最大值,得到数组最大值
                  第2轮,arr.length - i - 1 =5-1-1=3:
                    第1次循环:arr[0]与arr[1]比较,不管怎么样,arr[1]结果是最大值
                    第2次循环:arr[1]与arr[2]比较,不管怎么样,arr[2]结果是最大值
                    第3次循环:arr[2]与arr[3]比较,不管怎么样,arr[3]结果是最大值,得到数组第二大值
                  第3轮:arr.length - i - 1 =5-2-1=2:
                    第1次循环:arr[0]与arr[1]比较,不管怎么样,arr[1]结果是最大值
                    第2次循环:arr[1]与arr[2]比较,不管怎么样,arr[2]结果是最大值,得到数组第三大值
                  第4轮:arr.length - i - 1 =5-3-1=1:
                    第1次循环:arr[0]与arr[1]比较,不管怎么样,arr[1]结果是最大值,arr[0]是最小值。
                 */
                if (arr[j] > arr[j+1]) {
                    int temp = arr[j];//max
                    arr[j] = arr[j+1];//min
                    arr[j+1] = temp;//max
                }
            }
        }
        //输出排序后的升序数组
        for (int x = 0; x <arr.length ; x++) {
            System.out.println(arr[x]);
        }
    }
}
//练习:降序如何写?

5. 方法

将一个特定功能的代码定义在一个大括号内,就是一个方法;当需要这个功能时,就去调用方法。

注意:

  • 方法定义的先后顺序无所谓。
  • 方法之间不能产生嵌套包含关系。(即功能与功能分开,不重叠交叉。)
  • 方法在定义完毕后,方法不会自己运行,必须被调用才能执行。

5.1 无参数方法定义

修饰符 返回值类型 方法名 (参数列表){
    代码...
    return 返回值;//void 可以不写
}
===========================================================
//public是公共的方法,可以被任何一个类调用。
//static是静态修饰,说明这个函数是属于类的,在调用时不需要再创建对象。
//void是返回值类型,表示没有返回值。返回值是从方法中出来的数据,因此void可以不写return。  
//方法名称()用小驼峰命名,它括号内的叫做参数列表,参数是进入方法的数据,目前是无参数格式。
public static void 方法名称() {
    方法体
}
============================================================
//private是私用的方法,也就是只能在本类中被调用,任何其他类都不能调用。
private static void 方法名称() {
    方法体
}
=============================================================
//方法格式1
public static void methodName() {
    System.out.println("这是一个方法");
}

5.2 方法的调用

本类调用

public class ClassA {
    public static void main(String[] args) {
        //调用定义的方法method
        method();
    }

    //定义方法,被main方法调用
    public static void method() {
        System.out.println("自己定义的方法,需要被main调用才会运行");
    }
}

另外一个类调用ClassA的方法(如果是private能被调用吗?)

public class ClassB {
    public static void main(String[] args) {
        //通过  类名.方法  来调用
        ClassA.method();
    }
}

5.3 注意事项

正确写法

public class Demo {
    public static void main(String[] args){
        method();    
} 
    //正确写法,类中,main方法外面可以定义方法
    public static void method(){
        
    }
}

错误写法

public class Demo {
    public static void main(String[] args){
    //错误写法,一个方法不能定义在另一方法内部
        public static void method(){
        
        }
    }
}

5.4 带参数方法定义、调用方式

修饰符 返回值类型 方法名 (参数列表){
    代码...
    return 返回值;
}
//修饰符:常用 public static
//返回值类型:是方法最终产生的数据结果是什么类型,不再是void;返回值类型与参数列表的数据类型可以不一致。
//方法名用小驼峰命名
/*参数列表分为:参数类型:进入方法的数据是什么类型;参数名称:进入方法的数据对应的变量名称。
如果有多个参数,使用逗号隔开。*/
//return:作用一:停止当前方法。作用二:将return后面的返回值提交给调用处。
//return后面的“返回值”,必须和“方法名”前面的“返回值类型”保持对应。
//return意味着方法结束,所有后面的代码永远不会执行,属于无效代码。强写错误。
//一个方法当中可以有多个return,比如if else语句,但是必须保证同时只有一个被执行。两个不能连写。

单独调用:会发现程序没有输出,但实际上方法已经调用了。

public class Demo {
    public static void main(String[] args) {
//调用方法getSum,传递两个整数,这里传递的实际数据称为实际参数(实参),并接收方法计算后的返回值。
        getSum(5, 6);//单独调用
    }

    //定义计算两个整数和的方法,注意返回值类型int可以改成double,只需把调用方法那里也改为double。
    public static int getSum(int a, int b) {//定义两个int参数,这里称为形式参数(形参)。
        int result = a + b;
        System.out.println("方法已被调用啦!");
        return result;
        //或者这样写:
        //return a + b; “返回值”,必须和“方法名”前面的“返回值类型”保持对应。
        //这里如果方法是double返回类型,则带了强制转换。
       
    }
}

打印调用:打印输出调用方法后的结果

public class Demo {
    public static void main(String[] args) {
        // 调用方法getSum,传递两个整数,这里传递的实际数据又称为实际参数(实参),
        // 并接收方法计算后的结果,返回值。
        System.out.println(getSum(5, 6));//打印调用
    }

    //定义计算两个整数和的方法,注意返回值类型int可以改成double,只需把调用方法那里也改为double。
    public static int getSum(int a, int b) {//定义两个int参数,这里称为形式参数(形参)。
        int result = a + b;
        System.out.println("方法已被调用啦!");
        return result;
        //或者这样写:
        //return a + b; “返回值”,必须和“方法名”前面的“返回值类型”保持对应。
        //这里如果方法是double返回类型,则带了强制转换。
       
    }
}

赋值调用

public class Demo {
    public static void main(String[] args) {
        // 调用方法getSum,传递两个整数,这里传递的实际数据又称为实际参数(实参),
        // 并接收方法计算后的结果,返回值。
        int sum = getSum(5, 6);//赋值调用;注意数据类型int与方法的“返回值类型”保持一致。
        sum +=10;
        System.out.println(sum);
    }

    //定义计算两个整数和的方法,注意返回值类型int可以改成double,只需把调用方法那里也改为double。
    public static int getSum(int a, int b) {//定义两个int参数,这里称为形式参数(形参)。
        int result = a + b;
        System.out.println("方法已被调用啦!");
        return result;
        //或者这样写:
        //return a + b; “返回值”,必须和“方法名”前面的“返回值类型”保持对应。
        //这里如果方法是double返回类型,则带了强制转换。
       
    }
}

注意:之前的void写法,只能单独调用,不能进行打印调用和赋值调用。

5.5 方法的调用流程图解

image-20210423232330444

5.6 方法重载(Overload)

  • 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返 回值类型无关。
  • 参数列表:个数不同,数据类型不同,顺序不同。
  • 重载方法调用:JVM通过方法的参数列表,调用不同的方法。

示例1:定义加法

//对于功能类似的方法来说,因为参数列表不一样,需要记住那么多不同的方法。如何解决?
public class Demo {
    public static void main(String[] args) {
        System.out.println(sumTwo(1, 2));
        System.out.println(sumThree(1,2,3));
        System.out.println(sumFour(1,2,3,4));
    }

    public static int sumTwo(int a, int b) {
        return a + b;
    }

    public static int sumThree(int a, int b, int c) {
        return a + b + c;
    }

    public static int sumFour(int a, int b, int c, int d) {
        return a + b + c + d;
    }
}

加法定义用重载,不需要记住每个方法名称,只需记住功能。

public class Demo {
    public static void main(String[] args) {
        System.out.println(average(1, 2));
        System.out.println(average(1,2,3));
        System.out.println(average(1,2,3,4));
    }

    public static double average(double a, double b) {
        return ( a + b )/2;
    }

    public static double average(double a, double b, double c) {
        return (a + b + c)/3;
    }

    public static double average(double a, double b, double c, double d) {
        return (a + b + c + d)/4;
    }
}

判断哪些方法是重载

public static void open(){}//正确重载
public static void open(int a){}//正确重载
static void open(int a,int b){}//代码错误,与第8行一样,冲突。
public static void open(double a,int b){}//正确重载
public static void open(int a,double b){}//与第6行冲突
public void open(int i,double d){}//与第5行冲突
public static void OPEN(){}//代码无报错,但不是重载。
public static void open(int i,int j){}//代码错误,与第3行冲突。

5.7 数组作为方法参数和返回值

数组作为方法参数传递,传递的参数是数组内存的地址。

public static void main(String[] args) {
    int[] arr = { 1, 3, 5, 7, 9 };
    //调用方法,传递数组
    printArray(arr);
} 
/*
创建方法,方法接收数组类型的参数
进行数组的遍历
*/
public static void printArray(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}

5.8 数组作为方法返回值

数组作为方法的返回值,返回的是数组的内存地址。

public static void main(String[] args) {
    //调用方法,接收数组的返回值
    //接收到的是数组的内存地址
    int[] arr = getArray();
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}
/*
创建方法,返回值是数组类型
return返回数组的地址
*/
public static int[] getArray() {
    int[] arr = { 1, 3, 5, 7, 9 };
    //返回数组的地址,返回到调用者
    return arr;
}

即:方法的参数为基本类型时,传递的是数据值;方法的参数为引用类型时,传递的是地址值。

6. 面向对象

Java语言是一种面向对象的程序设计语言。

这里的对象泛指现实中一切事物,每种事物都具备自己的属性行为

面向对象思想就是参照现实中的事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。

它区别于面向过程思想(C语言),强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。

举例

  • 洗衣服:
    • 面向过程:找一个盆-->放衣服-->放洗衣粉-->加水-->浸泡10分钟-->揉一揉-->清洗衣服-->拧干-->晾起来
    • 面向对象:打开全自动洗衣机-->扔衣服-->按钮-->晾起来

区别

  • 面向过程:强调步骤。
  • 面向对象:强调对象,这里的对象就是洗衣机。

**特点 **

面向对象更符合人的思考习惯,将复杂的事情简单化,让我们从执行者变成了指挥者。

看个实际代码理解

面向过程(打工人):当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节。 面向对象(老板):当需要实现一个功能的时候,不关心具体的步骤,而是找一个具备该功能的人,来帮我做事。

import java.util.Arrays;
public class ClassA {
    public static void main(String[] args) {
        int[] array = {10, 20, 30, 40, 50};
        //要求打印格式为:[10,20,30,40,50]
        //1.使用面向过程,你得自己写好每一步骤,造轮子
        System.out.print("[");
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                System.out.println(array[i] + "]");
            } else {
                System.out.print(array[i] + ", ");
            }
        }
        System.out.println("=========================");
        //2.使用面向对象,直接拿轮子来用(用JDK自带的Arrays类的toString方法)
        System.out.println(Arrays.toString(array));
    }
}

如上可见,收集轮子是很重要的一件事,轮子一般自己写,或者"看"别人写的,开发就是重复搬砖工作。

6.1 类和对象

是一组相关属性行为的集合,可以看成是一类事物的模板。使用属性特征和行为特征来描述该类事物。

属性:就是该事物的状态信息。

行为:就是该事物能够做什么。

比如:

  • ,就是一个类
    • 属性:名字、体重、年龄、颜色。
    • 行为:走、跑、叫。

对象是一类事物的具体体现,对象是类的一个实例。具备该类事物的属性和行为。

比如:

  • 一只猫
    • 属性:小强、5kg、2 years、black。
    • 行为:溜墙根走、蹦跶的跑、喵喵叫。

**类与对象的关系 **

  • 类是对一类事物的描述,是抽象的。如,猫科动物

  • 对象是一类事物的实例,是具体的。如,黑猫、白猫能抓老鼠的都是好猫

  • 类是对象的模板,对象是类的实体

实例化一个对象

<img src="Java精讲精练.assets/image-20210426194808559.png" alt="image-20210426194808559" style="zoom:50%;" />

6.2 类的定义

Java中用class描述事物:

  • 类的成员变量:对应事物的属性。
  • 类的成员方法:对应事物的行为。
public class ClassName {
    //成员变量
    //成员方法
}
  • 定义类:就是定义类的成员,包括成员变量和成员方法。

  • 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。

  • 成员方法:和以前定义方法几乎是一样的。只不过把static去掉。<font color='red'>没有,没有static!</font>

//创建类(定义类)
//在IDEA的src下新建一个包:myclass,在myclass包里新建一个Java Class命名Cat
package mycalss;//包管理:方便类的查找和使用。
public class Cat {
    //猫的成员变量,如果想在当前包外使用得加上public
    String name;//姓名,以前写的叫局部变量,是写在方法内部的,现在是直接写在类当中。
    double weight;//体重,默认KG
    int age;//年龄
    String color;//姓名
        
    //猫的成员方法
    //猫的走路方法
    public void walk() {//不总是void,可以是别的返回值类型:int、String等
        System.out.println("溜墙根走");
    } 
    //跑的方法
    public void run() {
        System.out.println("蹦跶的跑");
    }
    //叫的方法
    public void talk() {
        System.out.println("喵喵叫");
    }
}

通常情况下,一个类并不能直接使用,需要根据类创建一个对象才能使用。

6.3 访问控制

针对类、成员方法、成员变量,有4种访问控制权限:

  • private:修饰类的成员变量、成员方法。只能在本类访问。
  • default:就是什么都不写,没有任何访问权限声明。可以被本包中其他类访问,但不能被其他包的类访问。
  • protected:只能被本包及不同包的子类访问。
  • public:所有类都可访问,不管是否在同一包中。

6.4 类的使用

**对象的使用格式 **

创建对象:

类名 对象名 = new 类名();
=============================
Cat cat1 = new Cat();//创建类的cat1对象

使用对象访问类中的成员:

对象名.成员变量;
对象名.成员方法();
===================
Cat1.name = "小猫";//给成员变量赋值
Cat1.run();//调用成员方法

**成员变量的默认值 **

数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) '\u0000'
布尔(boolean) false
引用类型 数组,类,接口 null

示例:

import mycalss.Cat;//导包,指出要用的类放在哪里,如果是同一个目录则不需要导包。
public class TestCat {
    public static void main(String[] args) {
        //创建对象格式:类名 对象名 = new 类名();
        Cat c = new Cat();
        System.out.println(c);//输出内存地址:mycalss.Cat@568db2f2
        //直接输出成员变量值默认值
        System.out.println(c.name);
        System.out.println(c.weight);
        System.out.println(c.age);
        System.out.println(c.color);
        System.out.println("===================");
        //给成员变量赋值
        c.name = "小猫";
        c.weight= 2.5;
        c.age= 3;
        c.color="黄色";
        //再次输出成员变量的值
        System.out.println(c.name);
        System.out.println(c.weight);
        System.out.println(c.age);
        System.out.println(c.color);
        System.out.println("===================");
        //调用成员方法
        c.walk();
        c.run();
        c.talk();
    }
}

6.5 对象内存图

6.5.1 一个对象,调用一个方法内存图

2021-04-26_235122

在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。 如上,main方法最后出栈。

6.5.2 两个对象,调用同一方法内存图

2021-04-27_001622

对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间

6.5.3 两个引用指向同一个对象的内存图

2021-04-27_002932

6.5.4 使用对象作为方法的参数

猫的类如下:

public class Cat {
    //猫的成员变量,如果想在当前包外使用得加上public
    String name;//姓名,以前写的叫局部变量,是写在方法内部的,现在是直接写在类当中。
    double weight;//体重,默认KG
    int age;//年龄
    String color;//姓名

    //猫的成员方法
    //猫的走路方法
    public void walk() {
        System.out.println("溜墙根走");
    }
    //跑的方法
    public void run() {
        System.out.println("蹦跶的跑");
    }
    //叫的方法
    public void talk() {
        System.out.println("喵喵叫");
    }
}

把猫类的对象作为方法参数传递

public class TestCat {
    public static void main(String[] args) {
        Cat one = new Cat();
        one.name = "小猫";
        one.weight= 2.5;
        one.age= 3;
        one.color="黄色";
        method(one);//实参one通过形参c,传递进方法内部。传递的是地址值。
    }
    public static void method(Cat c){
        System.out.println(c.name);//小猫
        System.out.println(c.weight);//2.5
        System.out.println(c.age);//3
        System.out.println(c.color);//黄色
    }
}

现在以手机为例,分析程序运行流程:

image-20210428202106239

6.5.5 使用对象类型作为方法的返回值

image-20210428203803328

6.6 成员变量和局部变量区别

变量根据定义位置的不同,我们给变量起了不同的名字。

public class Car {
    String color;//成员变量

    public void drive(int a) {//方法的参数也是局部变量
        System.out.println(a);//因形参变实参必然被赋值了,所以可以直接使用。
        double speed = 80;//局部变量
        System.out.println("车速:" + speed);
        System.out.println(color);//可以使用
    }
}
  • 在类中的位置不同

    • 成员变量:类中,方法外
    • 局部变量:方法中或者方法声明上(形式参数)
  • 作用范围不一样

    • 成员变量:类中
    • 局部变量:方法中
  • 初始化值的不同

    • 成员变量:有默认值
    • 局部变量:没有默认值。必须先定义,赋值,最后使用
  • 在内存中的位置不同

    • 成员变量:堆内存
    • 局部变量:栈内存
  • 生命周期不同

    • 成员变量:随着对象的创建而存在,随着对象的消失而消失
    • 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失

7. 封装

7.1 封装概述

封装性在Java中的体现:

  • 方法就是一种封装
  • 关键字private也是一种封装

封装将一些代码隐藏起来,外界无法直接操作和修改,要访问它,必须通过指定的方式。

封装是一个保护屏障,防止封装的代码被随意访问,加强了代码的安全性。

适当的封装可以让代码更容易理解与维护,因为你不用关注他用什么代码实现。

封装原则

属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。

7.2 封装的步骤

  • 使用 private 关键字来修饰成员变量。 也可以修饰成员方法,但没有意义。
  • 外部想要访问成员变量,本类必须提供对应的一对 getXxx 方法 、 setXxx 方法。
public class Person {
    String name;//姓名
//age成员变量不加修饰符时,无法阻止不合理的数值被设置进来,比如:-20;
//用了private后,本类当中可以随意访问,超出本类不能直接,直接访问。
    private int age;//年龄
    public void say(){
        System.out.println("我叫:" +name+",年龄:"+ age);
    }
    
    //提供一对getXxx方法 、 setXxx方法。命名固定了。
    //这个成员方法setAge(),必须没有返回值,专门用于向成员变量age设置数值。
    public void setAge(int num) {//setAge必须这样用小驼峰写;Age与成员变量age对应。
        if (num < 150 && num > 1) {
            age = num;
        } else {
            System.out.println("数据错误!");
        }
    }

    //这个成员方法getAge(),必须有返回值,专门用于返回age数值。
    public int getAge() {//名称定死,必须是get+Age
        return age;
    } 
}

使用Person类

public class UsePerson {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        
        person.age = 20;//不能访问了,报错!
        
        person.setAge(-20);//输出:数据错误!
        person.say();//我叫:小明,年龄:0    其中0为默认值
        person.setAge(20);
        System.out.println("年龄:" + getAge());//注意获取用getAge() !!!
        person.say();//我叫:小明,年龄:20
    }
}

注意:对于boolean成员变量,get方法一要写成isXxx的形式,而setXxx规则不变。

其他类调用赋值时写对象名.setXxx(true);或者对象名.setXxx(false);

7.3 封装优化1-this关键字

先看案例:

//定义一个Person类
public class Person {
    String name;//自己的名字

    //name是成员变量,自己的名字
    //who是形参,对方的名字
    public void sayhello(String who){
        System.out.println(who + ",你好,我是" + name);
    }
}
//使用
public class Use {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        person.sayhello("小红");//小红,您好,我是小明
    }
}

如果程序改为:

//定义一个Person类
public class Person {
    String name;//自己的名字

    //name是成员变量,自己的名字
    //name是形参,对方的名字
    public void sayhello(String name){
        System.out.println(name + ",你好,我是" + name);
    }
}
//使用
public class Use {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        person.sayhello("小红");//小红,你好,我是小红
    }
}

如上,当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。

如果需要访问本类当中的成员变量,需要使用格式:this.成员变量名

因此程序可以写为如下,运行正确。

//定义一个Person类
public class Person {
    String name;//自己的名字

    //name是成员变量,自己的名字
    //name是形参,对方的名字
    public void sayhello(String name){
        System.out.println(name + ",你好,我是" + this.name);
    }
}
//使用
public class Use {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        person.sayhello("小红");//小红,你好,我是小明
    }
}

this可以解决重名问题,this必须写在方法内部。(为什么重名,因为重名可以见名知意,便于阅读。)

方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

示例:this代表所在类的当前对象的地址值,即对象自己的引用 。

//定义一个Person类
public class Person {
    String name;//自己的名字

    //name是成员变量,自己的名字
    //name是形参,对方的名字
    public void sayhello(String name){
        System.out.println(name + ",你好,我是" + this.name);
        System.out.println(this);//Person@4e50df2e,地址值一样
    }
}
//使用
public class Use {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        person.sayhello("小红");//小红,你好,我是小明
        System.out.println(person);//Person@4e50df2e,地址值一样
    }
}

上面的代码是有两个变量的,小明和小红就是两个变量赋值结果,为了区分所以用this;另外,方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写 。

7.4 封装优化2-构造方法

当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

当我们通过关键字new来创建对象时,其实就是在调用构造方法。

无论你是否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java提供的默认无参数构造方法就会失效。

构造方法的定义格式

修饰符 构造方法名(参数列表){
    方法体
}
  • 构造方法名必须与类名称完全一致;
  • 构造方法不能有return返回值!不需要void

案例,调用构造方法理解:

//定义Person类
public class Person {
    //无参数构造方法
    public Person(){
        System.out.println("无参数构造方法执行了!");
    }
}
//使用,运行结果:无参数构造方法执行了!
public class Use {
    public static void main(String[] args) {
        Person person = new Person();//注意语句右边,只要new就调用无参数的构造方法Person()
    }
}

如果你没有写构造方法(像之前的案例),那Java编译器会赠送一个给你,它没有参数、方法体,什么事情做不做,就会new。一旦你自己写了构造方法,编译器将不再赠送,因为你自力更生了。

public class Person {//理解本类只有一个构造方法
    private String name;
    private int age;
    //有参数构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

构造方法可以重载

public class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("无参数构造方法执行了!");
    }

    public Person(String name, int age) {
        System.out.println("有参数构造方法执行了!");
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {//就算构造方法有参数,set方法还是有必要定义的。
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {//就算构造方法有参数,set方法还是有必要定义的。
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}
//调用
public class Use {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person("小明", 18);
        System.out.println(person2.getName()+"今年"+person2.getAge()+"岁");
        //如果需要改变对象当中的成员变量数据内容;比如年龄,仍然还需要使用setXxx方法。
        //改变年龄
        person2.setAge(19);
        System.out.println(person2.getName()+"今年生日后是"+person2.getAge()+"岁");
    }
}
结果:
无参数构造方法执行了!
有参数构造方法执行了!
小明今年18岁
小明今年生日后是19岁

总结

  • 如果你不提供构造方法,系统会给出无参数构造方法。
  • 如果你提供了构造方法,系统将不再提供无参数构造方法。
  • 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

7.5 定义一个标准的类

JavaBean 是 Java语言编写类的一种标准规范。

一个标准的类通常要有以下四个部分:

  1. 所有的成员变量都要使用private关键字修饰
  2. 为每一个成员变量编写一对set、get方法
  3. 编写一个无参数的构造方法
  4. 编写一个全参数的构造方法(有几个成员变量就相应有几个参数)

还是以之前的Person类为例:

//标准的类写法
public class Person {
    private String name;//姓名
    private int age;//年龄

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

如上,我们其实只关注nameage,但却写了一堆代码。我只想写nameage的两行代码,其它都不想动手。

第一步:输入:

public class Person {
    private String name;
    private int age;
    
}

第二步:生成无参数构造方法:把光标放在到第4行,Code-->>generate(生成)【或者按快捷键:alt+insert】

rrt55

第三步:生成全参数构造方法(按住shift或ctrl可多选参数)

rrt556

第四步:生成set、get方法

rrt5566

8. 常用API第一部分

API(Application Programming Interface),应用程序编程接口。

Java API是一本程序员的 字典 ,是JDK中提供给我们使用的类的说明文档。

这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。

所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们

8.1 API使用

JDK_API_1_6_zh_CN.CHM官方(sun)翻译版本,后续的官方不再翻译。

基本使用:

双击打开CHM帮助文档,点击显示,找到索引,看到输入框。在输入框里输入Scanner,然后两次enter确认。

rrt55664

8.2 Scanner类

一个可以解析基本类型和字符串的简单文本扫描器。可以实现把键盘输入的数据传递到程序当中。

使用步骤

1、导包

首先根据上一节的方法,查询API文档,发现Scanner 类在java.util包下。(java.lang包下的所有类无需导入

image-20210514212104922

示例:

//导包的语句需要定义在类的上面。
//格式:import 包名.类名;
import java.util.Scanner; 

2、创建对象(在构造方法摘要查看)

使用该类的构造方法,创建一个该类的对象。

image-20210514213055851

<img src="Java精讲精练.assets/image-20210514213739731.png" alt="image-20210514213739731" style="zoom:80%;" />

示例:

//格式:数据类型 变量名 = new 数据类型(参数列表);
//System.in:读取标准输入设备数据。
Scanner sc = new Scanner(System.in);// 创建Scanner对象,sc表示变量名,其他均不可变

3、调用方法(在方法摘要查看)

调用该类的成员方法,完成指定功能。

<img src="Java精讲精练.assets/image-20210514214419478.png" alt="image-20210514214419478" style="zoom:80%;" />

示例:

//格式:变量名.方法名();
int i = sc.nextInt(); // 表示将键盘录入的值作为int数返回。

一个完整示例:

//1.导包
import java.util.Scanner;
public class DemoScanner {
    public static void main(String[] args) {
    //2.创建对象
    Scanner sc = new Scanner(System.in);//System.in从键盘获取数据,接收的都是字符串!!
    //3. 接收数据
    System.out.println("请录入一个整数:");//注意是整数,输入浮点数试试!
    int i = sc.nextInt();//从键盘获取输入的字符串转换为int数字
    //4. 输出数据
    System.out.println("录入的整数是:" + i);
    }
}

练习:求和、取最值

8.3 匿名对象

没有名称的对象就叫匿名对象。

基于一个类来讲解:

public class Cat {
    String name;
    
    public void showName(){
        System.out.println("我叫:" + name);
    }
}

一般情况创建对象标准格式为:

//类名称 对象名称 = new 类名称();
Cat cat1 = new Cat();

匿名对象格式:

//只有右边,没有左边
new Cat();

示例:

public class Use {
    public static void main(String[] args) {
        //常规使用
        Cat cat1 = new Cat();
        cat1.name = "小黄";
        cat1.showName();//我叫:小黄
        //匿名对象
        new Cat().name = "小白";
        new Cat().showName();//我叫:null   因为new操作是新建了一个对象,并没有赋值!
    }
}

注意:匿名对象只能使用唯一的一次,下次再用不得不再创建一个新对象。 建议:如果确定有一个对象只需要使用唯一的一次,就可以用匿名对象。

Scanner匿名写法

//普通使用方式
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
        
//匿名使用方式
int num2 = new Scanner(System.in).nextInt();

8.3.1 匿名对象作为方法的参数

import java.util.Scanner;
public class Use {
    public static void main(String[] args) {
        //普通使用方式
        //Scanner sc = new Scanner(System.in);
        //methodParam(sc);

        //匿名使用方式
        methodParam(new Scanner(System.in));
    }
    //方法
    public static void methodParam(Scanner sc) {
        int i = sc.nextInt();
        System.out.println(i);
    }
}

8.3.2 匿名对象作为方法的返回值

import java.util.Scanner;
public class Use {
    public static void main(String[] args) {
        Scanner sc = methodReturn();
        int i = sc.nextInt();
        System.out.println(i);

    }

    public static Scanner methodReturn() {
        //普通方式
        //Scanner sc = new Scanner(System.in);
        //return sc;
        
        //匿名方式
        return new Scanner(System.in);
    }
}

8.4 Random类

使用步骤

1、查询类:import导入

<img src="Java精讲精练.assets/image-20210517000634352.png" alt="image-20210517000634352" style="zoom: 67%;" />

import java.util.Random;

**2、查看构造方法 **

<img src="Java精讲精练.assets/image-20210517000856140.png" alt="image-20210517000856140" style="zoom:67%;" />

Random r = new Random();

**3、查看成员方法 **

<img src="Java精讲精练.assets/image-20210517001202454.png" alt="image-20210517001202454" style="zoom:80%;" />

int i = r.nextInt();//取值范围是int所有数据,有正负两种
int x = r.nextInt(10);//取值范围是0至(10-1)之间随机

一个完整的示例

import java.util.Random;//导包
public class Use {
    public static void main(String[] args) {
        Random r = new Random();//构造就是new
        
        int i= r.nextInt();//成员方法
        System.out.println(i);
        
        int x=r.nextInt(10);//成员方法
        System.out.println(x);
    }
}

练习:获取1-n之间的随机整数,包含n;随机生成双色球彩票。

8.5 ArrayList类

8.5.1 对象数组

首先学习什么是对象数组

示例:定义一个数组,用来存储3个Person对象

//首先用标准类写法,创建一个Person类
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
}

定义对象数组

public class ClassB {
    public static void main(String[] args) {
        //创建一个长度为3的动态数组,里面用来存储Person类型的对象
        Person[] arry = new Person[3];
        System.out.println(arry[0]);//打印结果为:null  为什么?  
        //因为数组是引用类型,引用类型的初始化是null
        Person one = new Person("小明", 18);
        Person two = new Person("小红", 16);
        Person three = new Person("小白", 28);
        //将对象的地址值赋值到数组元素,元素是存储的地址值!
        arry[0] = one;
        arry[1] = two;
        arry[2] = three;
        System.out.println(arry[0]);//打印结果为类似:Person@568db2f2的地址值,与第5行区别?
        System.out.println(arry[1]);//地址值
        System.out.println(arry[2]);//地址值
        System.out.println(arry[0].getName());//小明


    }
}

以上是对象数组基本用法,但是数组有一个缺点:一旦创建,程序运行期间长度不可以发生改变。

一般程序开发时,定不下来要几个元素,还在考虑变化中,如果你创建小了,后面又想加大数组,会非常麻烦!

8.5.2 ArrayList

ArrayList集合的长度可以随意变化。

<img src="Java精讲精练.assets/image-20210519223252612.png" alt="image-20210519223252612" style="zoom: 50%;" />

<E>代表泛型。 泛型: E取自Element(元素)的首字母。也就是装在集合当中的所有元素都用统一的什么类型。 注意:泛型只能是引用类型,不能是基本类型。

<img src="Java精讲精练.assets/image-20210519223704899.png" alt="image-20210519223704899" style="zoom:50%;" />

<img src="Java精讲精练.assets/image-20210519223753765.png" alt="image-20210519223753765" style="zoom:50%;" />

完整示例:

import java.util.ArrayList;
public class DemoList {
    public static void main(String[] args) {
        //创建了一个名称为list的ArrayList集合,里面装的都是String字符串类型的数据
        //备注:从JDK1.7+开始,右侧的<>中的内容可以不写。
        //课本未写<>,不是一个严谨的写法。
        ArrayList<String> list = new ArrayList<>();
        //对于ArrayList类来说,直接打印得到的不是地址值,而是内容(某个大神写的规则)。如果内容为空,得到的是空的中括号:[]
        System.out.println(list);//打印结果:[]
        //向集合中添加数据,需要用add成员方法
        list.add("小明");
        System.out.println(list);//[小明]
        list.add("小红");
        System.out.println(list);//[小明, 小红]
        list.add("小白");
        System.out.println(list);//[小明, 小红, 小白]
        //list.add(100);错误
    }
}

8.5.3 常用方法和遍历

对于元素的操作,基本体现在——增、删、查。常用的方法有:

  • public boolean add(E e) :向集合中添加元素(添加的在尾部),参数的类型和泛型一致。
  • public E remove(int index) :从集合中删除元素,参数是索引编号,返回值是对应编号被删除掉的元素。
  • public E get(int index) :从集合中获取元素,参数是索引编号,返回值是对应编号的元素。
  • public int size() :获取集合的长度,返回值是集合中包含的元素个数。
//常用方法
import java.util.ArrayList;
public class DemoList {
    public static void main(String[] args) {
        //创建ArrayList集合,代码都是万年不变
        ArrayList<String> list = new ArrayList<>();
        
        //向list添加元素,add方法返回值是布尔类型,即是否成功。
        //为什么返回是布尔,对于ArrayList的add来说,都是true的,没必要写boolean代码,但其它集合不一定能true。
        boolean success = list.add("小明");
        System.out.println("元素是否添加成功:" + success);//true
        System.out.println(list);//[小明]
        list.add("小红");
        list.add("小哎");
        System.out.println(list);//[小明, 小红, 小哎]
        
        //从集合中获取元素,get
        String name = list.get(2);
        System.out.println("第2号元素是:" + name);//[小哎]
        
        //从集合中删除元素,remove
        String whoRemoved = list.remove(1);
        System.out.println("被删除的人是:" + whoRemoved);//小红
        System.out.println(list);//[小明, 小哎]
        
        //获取集合长度
        int size = list.size();
        System.out.println("该集合长度是:" + size);//2
    }
}
//遍历集合
import java.util.ArrayList;
public class DemoList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("小明");
        list.add("小红");
        list.add("小哎");
        //遍历集合
        for (int i = 0; i < list.size(); i++) {//怎么快速输入这行?list.fori
            System.out.println(list.get(i));
        }
    }
}

8.5.3 集合存储基本数据类型

ArrayList对象不能存储基本类型,只能存储引用类型的数据。类似 <int> 不能写

ArrayList想存储基本数据类型,必须使用基本类型对应的”包装类“,做个“包装”,当对象使用。

基本类型 基本类型包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

我们发现,只有 Integer 和 Character 需要特殊记忆,其他基本类型只是首字母大写即可。

JDK1.5+开始,支持自动装箱、自动拆箱。 自动装箱:基本类型 -->包装类型(引用类型) 自动拆箱:包装类型 -->基本类型

因此,自动装拆箱相当于直接用基本类型,示例:

import java.util.ArrayList;
public class DemoList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);//[1, 2, 3]
    }
}

练习:

  1. 生成6个1~33之间的随机整数,添加到集合,并打印遍历输出。
  2. 自定义4个学生对象,添加到集合,并打印遍历输出。

8.6 String类

程序当中所有的双引号字符串,都是String类的对象。(就算没有new,也照样是。)

特点

1.字符串是常量,一旦创建永不可变。

String s1 = "abc";
s1 += "d";//"d"未创建
System.out.println(s1); // "abcd"
// 内存中有"abc","abcd"两个对象,s1从指向"abc",改变指向到了"abcd"

2.正是因为字符串不可改变,所以字符串是可以共享使用的。(节省内存)

String s1 = "abc";
String s2 = "abc";
// 内存中只有一个"abc"对象被创建,同时被s1和s2共享。

3.字符串效果上相当于是char[]字符数组。"abc" 等效于 char[] data={ 'a' , 'b' , 'c' }

String str1 = "abc";
System.out.println(str1);//abc

//相当于:
char data[] = {'a', 'b', 'c'};
String str2 = new String(data);
System.out.println(str2);//abc

// String底层是靠字符数组实现的。

8.6.1 String常见构造方法

常见3+1种new字符串方式:三种构造方法、一种直接创建。

  • 三种构造方法
    • public String() :创建一个空白字符串,不含有任何内容。
    • public String(char[] value) :根据字符型数组的内容,来创建对应的字符串。
    • public String(byte[] bytes) :根据字节数组的内容,来创建对应的字符串。
public class Demo {
    public static void main(String[] args) {
        // 无参构造
        String str1 = new String();
        System.out.println(str1);//什么都没有
        
        // 通过字符数组构造
        char c[] = {'中', '国','人','a','b','c'};//跟字符集相关。
        String str2 = new String(c);
        System.out.println(str2);//中国人abc
        
        // 通过字节数组构造
        byte b[] = { 97, 98, 99 };
        String str3 = new String(b);
        System.out.println(str3);//abc  它是通过默认字符集解码的,中文涉及到编码等后续再说
    }
}
  • 直接创建:虽然没有new,但编译器默认给你new了,因此你创建的是字符串对象
String str = "abc";

8.6.2 字符串的常量池

程序当中直接写上的双引号字符串,就在字符串常量池中。

对于基本类型来说,==是进行数值的比较。 对于引用类型来说,==是进行地址值的比较。

public class Demo {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        char[] charArray = {'a', 'b', 'c'};
        String str3 = new String(charArray);
        System.out.println(str1 == str2);//true
        System.out.println(str1 == str3);//false
        System.out.println(str2 == str3);//false
    }
}

image-20210521222110579

8.6.3 字符串的比较方法

==是对象的地址值比较,如果确实需要字符串的内容比较,可以使用两个方法。

  • public boolean equals (Object anObject) : 参数可以是任何对象,只有参数是一个字符串且内容相同才会返回true,否则返回false。
  • public boolean equalsIgnoreCase (String anotherString) :忽略字母大小写进行内容比较。
public class Demo {
    public static void main(String[] args) {
        String str1="Hello";
        String str2="Hello";
        char[] c = {'H','e','l','l','o'};
        String str3=new String(c);

        //equals方法具有对称性,也就是str1.equals(str2)和str2.equals(str1)效果一样。
        System.out.println(str1.equals(str2));//true
        System.out.println(str2.equals(str3));//true

        //但是比较双方有一个常量、一个变量时,推荐把常量字符串写在前面。推荐:"Hello".equals(str1);不推荐:str1.equals("Hello");
        System.out.println("Hello".equals(str1));//true
        System.out.println("hello".equals(str2));//false  equals()是区分字母大小写的
        
        //为什么不推荐:str1.equals("Hello");
        String str4=null;
        System.out.println("Hello".equals(str4));//false 推荐常量在前。
        //System.out.println(str4.equals("Hello"));//出现空指针异常   不推荐变量在前。

        //忽略字母大小写
        System.out.println("hello".equalsIgnoreCase(str1));//true
    }
}

8.6.4 字符串的获取方法

  • public int length () :统计字符串的字符个数,返回此字符串的长度。
  • public String concat (String str) :将当前字符串和参数字符串拼接成新的字符串返回。
  • public char charAt (int index) :获取指定索引位置的单个字符。
  • public int indexOf (String str) :查找参数字符串在本字符串中首次出现的索引位置,如果没有返回-1值
  • public String substring (int beginIndex) :截取从参数位置一直到字符串末尾,返回截取后新字符串。
  • public String substring (int beginIndex, int endIndex) :截取从beginIndex开始,一直到endIndex结束,中间的字符串。且包含beginIndex,不包含endIndex。
public class Demo {
    public static void main(String[] args) {
        //获取字符串的长度
        int length = "aaffggkjgk".length();//返回值int
        int length1 = "中国".length();
        System.out.println(length);//10
        System.out.println(length1);//2

        //拼接字符串
        String str1 = "Hello";
        String str2 = "World";
        //简单方法
        String str3 = str1 + str2;
        System.out.println(str3);//HelloWorld
        //谁在前面,谁排前面
        String str4 = str1.concat(str2);//返回值是String
        System.out.println(str4);//HelloWorld   是新的字符串
        String str5 = str2.concat(str1);
        System.out.println(str5);//WorldHello   是新的字符串
        //注意拼接后,str1与str2并未改变,原封不动

        //获取指定索引位置的单个字符
        char c = str1.charAt(0);//返回char
        System.out.println(c);//H

        //查找参数字符串在本字符串中首次出现的索引位置,如果没有返回-1值
        int index = str1.indexOf("lo");//返回int
        System.out.println(index);//3

        //截取从参数位置一直到字符串末尾,返回截取后新字符串。
        String str6 = "HelloWorld";
        String str7 = str6.substring(5);
        System.out.println(str6);//HelloWorld  原封不动,未改变
        System.out.println(str7);//World  新的字符串

        //截取从beginIndex开始,一直到endIndex结束。但是包含beginIndex,不包含endIndex。
        String str8 = str6.substring(4, 7);//实际取就是4,5,6编号的元素,不含7号元素。
        System.out.println(str8);//oWo

        //对字符串常量不可改变的理解
        String strA = "Hello";
        System.out.println(strA);//Hello
        strA = "Java";
        System.out.println(strA);//Java
        //如上,strA字符串变了,是不是有违字符串常量不可改变?
        //理解:这里有两个字符串常量:"Hello"、"Java";strA中保存的是地址值,strA的改变其实只是地址值的改变 
    }
}

8.6.5 字符串的转换方法

  • public char[] toCharArray () :将当前字符串拆分为字符数组作为返回值。
  • public byte[] getBytes () :使用平台的默认字符集将该 String编码转换为新的字节数组。(仅限英文)
  • public String replace (CharSequence target, CharSequence replacement) :将与target匹配的字符串使用replacement字符串替换。 返回替换后的新字符串。
public class Demo {
    public static void main(String[] args) {
        //转换成为字符数组
        char[] c = "Hello".toCharArray();
        System.out.println(c[0]);//H

        //转换为字节数组
        String str1 = "abc";//请输英文
        byte[] b = str1.getBytes();
        System.out.println(b[1]);//98

        //查找替换,可以屏蔽“问候语”
        String str2 = "Hello World.";
        String str3 = str2.replace("World", "Java");
        System.out.println(str3);//Hello Java.
        //屏蔽“问候语”
        String str4 = "我问候语";
        String str5 = str4.replace("问候语", "***");
        System.out.println(str5);//我***
    }
}

8.6.6 字符串的分割方法

  • public String[] split(String regex) :将此字符串按照给定的regex(正则表达式)拆分为字符串数组
public class Demo {
    public static void main(String[] args) {
        //分割字符串1
        String str1="aaa,bbb,ccc";
        String[] array1=str1.split(",");
        for (int i = 0; i < array1.length; i++) {
            System.out.println(array1[i]);
        }
        //分割字符串2
        String str2="AAA.BBB.CCC";
        String[] array2=str2.split("\\.");
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i]);
        }
    }
}

注意:regex参数是一个正则表达式,如果按照英文句点分割,必须写\\.

练习:键盘输入一个字符串,统计其中各种字符出现的次数;种类有:大写字母、小写字母、数字、其他。

8.7 static关键字

先看一个例子:所在教室 每个对象都一样,共享才省内存空间。

image-20210524195012071

加入static关键字,所有对象共用同一份数据,要改动时只在类中改一次即可。

image-20210524195442280

8.7.1 static关键字修饰成员变量

如果一个成员变量使用了static关键字,那么这个变量不再属于对象自己,而是属于所在的类。多个对象共享同一份数据。

示例:生成一个Student类

public class Student {
    private String name;
    private int age;
    private static String room;//所在教室,这里可以写public,比较方便使用。

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getRoom() {
        return room;
    }

    public static void setRoom(String room) {
        Student.room = room;
    }
}

使用:

public class Use {
    public static void main(String[] args) {
        Student.setRoom("401教室");//只需赋值一次。建议使用这种方法,表示是静态变量
        Student one =new Student("小明",18);
        one.setRoom("401教室");//可以,不推荐,误以为是成员变量
        System.out.println("姓名:"+one.getName()+" 年龄:"+one.getAge()+" 教室:"+Student.getRoom());//类名称调用room
        Student two =new Student("小红",16);
        System.out.println("姓名:"+two.getName()+" 年龄:"+two.getAge()+" 教室:"+two.getRoom());//也可以对象名称调用room
    }
}
//结果:
//姓名:小明 年龄:18 教室:401教室
//姓名:小红 年龄:16 教室:401教室

8.7.2 static关键字修饰成员方法

一旦使用static修饰成员方法,就变成了静态方法。静态方法不属于对象,而是属于类。

如果成员方法没有static关键字,那么必须先创建对象,然后才能通过对象调用它。

注意:

  • 静态不能直接访问非静态:因为在内存当中是【先】有静态内容,【后】有非静态内容。非静态还没有new出来。
  • 静态方法中不能使用this,因为跟对象没关系,跟类才有关系。
public class Myclass {
    int num;//成员变量
    static int staticnum;//静态变量
    
    //成员方法不带不带static,之前学的
    public void method(){
        System.out.println("这是一个成员方法");
        //成员方法可以访问成员变量
        System.out.println(num);
        //成员方法可以访问静态变量
        System.out.println(staticnum);
    }
    
    //静态方法,因为带了static
    public static void staticMethod(){
        System.out.println("这是一个静态方法");
        //静态方法可以访问静态变量
        System.out.println(staticnum);
        //静态不能直接访问非静态,非静态还没new出来
        System.out.println(num);错误!
    }
}
public class Use {
    public static void main(String[] args) {
        //先创建后使用
        Myclass one=new Myclass();
        one.method();
        
        //对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用。
        one.staticMethod();//可以,不推荐,容易被误解为成员方法
        Myclass.staticMethod();//正确,推荐
        
        //对于本类当中的静态方法,可以省略类名称。
        method();
    }
    //本类静态方法
    public static void method(){
        System.out.println("自己的方法");
    }
}

如果有了static关键字,那么就不需要创建对象,直接就能通过类名称来使用它。

总结:

无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。 静态变量:类名称.静态变量 静态方法:类名称.静态方法()

8.7.3 static的内存图

static 修饰的内容:

  • 是随着类的加载而加载的,且只加载一次。
  • 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  • 它优先于对象存在,所以,可以被所有对象共享。

image-20210524212133197

8.7.4 static静态代码块

在类里直接写static{},注意不是在main方法里写

public class ClassName{
    
    static {
    //执行语句
    }
}

特点:当第一次用到该类时,静态代码块只执行唯一的一次。

静态内容总是优先于非静态,所以静态代码块比构造方法先执行。

public class Person {
    static {
        System.out.println("静态代码块执行了!");
    }
    public Person(){
        System.out.println("构造方法执行了!");
    }
}
public class Use {
    public static void main(String[] args) {
        Person one=new Person();
        /* 只有one时执行结果:
        静态代码块执行了!
        构造方法执行了!
        */
        Person two = new Person();
         /* 加上two后执行结果:(静态不会再执行了!)
        静态代码块执行了!
        构造方法执行了!
        构造方法执行了!
        */
    }
}

静态代码块的典型用途:用来一次性地对静态成员变量进行赋值。

8.8 Arrays类

java.util.Arrays 此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法。

  • public static String toString(int[] a) :将数组按默认格式变成字符串
  • public static void sort(int[] a) :对指定的 int 型数组按数字升序进行排序。
import java.util.Arrays;
public class ClassA {
    public static void main(String[] args) {
        //定义数组
        int[] arr = {23, 4, 6, 8, 27};
        //数组转字符串
        String str1 = Arrays.toString(arr);
        System.out.println(str1);//[23, 4, 6, 8, 27]
        //升序
        Arrays.sort(arr);//返回值是void,即直接把arr排好了,arr已经改变
        System.out.println(Arrays.toString(arr));//[4, 6, 8, 23, 27]

        //字母排序
        String[] arr2 = {"ccc", "aaa", "fff"};
        Arrays.sort(arr2);//按编码表的int值来排序
        System.out.println(Arrays.toString(arr2));//[aaa, ccc, fff]
    }
}

练习

8.8 Math类

java.lang.Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象。

  • public static double abs(double a) :返回 double 值的绝对值
double d1 = Math.abs(‐5); //d1的值为5
double d2 = Math.abs(5); //d2的值为5
  • public static double ceil(double a) :向上取整(不是四舍五入)
double d1 = Math.ceil(3.3); //d1的值为 4.0
double d2 = Math.ceil(‐3.3); //d2的值为 ‐3.0
double d3 = Math.ceil(5.1); //d3的值为 6.0
  • public static double floor(double a) :向下取整(不是四舍五入)
double d1 = Math.floor(3.3); //d1的值为3.0
double d2 = Math.floor(‐3.3); //d2的值为‐4.0
double d3 = Math.floor(5.1); //d3的值为 5.0
  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
long d1 = Math.round(5.5); //d1的值为6.0
long d2 = Math.round(5.4); //d2的值为5.0

Math.PI圆周率

double pi= Math.PI;
System.out.println(pi);//3.141592653589793

9. 继承

继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接 访问父类中的非私有的属性和行为。

2021-05-24_225052

如上图:

  • 大徒弟、小大徒弟都从师父那里学到(继承)了武功绝学,当然学到的都是师父愿意教的,可能绝招没教。
  • 讲师类和助教类、行政类等都有姓名、工号属性,我每写一个类时都写上两个属性,想想不太对。
  • 因此,继承主要解决的问题就是:共性抽取。提高代码的复用性

9.1 继承的格式

定义父类的格式:(一个普通的类定义)

public class 父类名称{
    //成员变量
    //成员方法
}

子类格式

public class 子类名称 extends 父类名称 {
    //成员变量
    //成员方法
}

在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。例如:父类是员工,子类是讲师,那么“讲师就是一个员工”。

示例:父类

//定义一个父类
public class Fu {
    public void fuMethod(){
        System.out.println("父类方法执行了!");
    }
}

子类

//定义一个子类,不写内容
public class Zi extends Fu {

}

使用:

public class Use {
    public static void main(String[] args) {
        Zi z =new Zi();
        z.fuMethod();//父类方法执行了!
    }
}

9.2 继承后的特点-成员变量

成员变量不重名时:

//父类
public class Fu {
    int numFu = 10;
}

//子类
public class Zi extends Fu {
    int numZi = 20;
}

//使用,没有重名,各项输出正常。
public class Use {
    public static void main(String[] args) {
        Fu fu=new Fu();//创建父类对象
        System.out.println(fu.numFu);//10  只能父类的东西,没有任何子类
        Zi zi= new Zi();
        System.out.println(zi.numFu);//10
        System.out.println(zi.numZi);//20
    }
}

成员变量重名时:

有父类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

  1. 直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。
  2. 间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
//1.等号左边是谁,就优先用谁

//父类
public class Fu {
    int numFu = 10;
    int num = 100;//重名
}

//子类
public class Zi extends Fu {
    int numZi = 20;
    int num = 200;//重名
}

//使用
public class Use {
    public static void main(String[] args) {
        Fu fu=new Fu();//创建父类对象
        System.out.println(fu.numFu);//10  只能父类的东西,没有任何子类
        Zi zi= new Zi();
        System.out.println(zi.numFu);//10
        System.out.println(zi.numZi);//20
        
        //等号左边是谁,就优先用谁
        System.out.println(zi.num);//200
    }
}
//2.间接通过成员方法访问成员变量

//父类
public class Fu {
    int numFu = 10;
    int num = 100;//重名
    
    public void methodFu(){
        //用的是本类当中的num,不可能找子类
        System.out.println(num);
    }
}

//子类
public class Zi extends Fu {
    int numZi = 20;
    int num = 200;//重名

    public void methodZi(){
        //首先优先查找本类,有num,所以使用本类的num,如果没有,则用父类的num
        System.out.println(num);
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Fu fu=new Fu();//创建父类对象
        System.out.println(fu.numFu);//10  只能父类的东西,没有任何子类
        Zi zi= new Zi();
        System.out.println(zi.numFu);//10
        System.out.println(zi.numZi);//20

        //等号左边是谁,就优先用谁
        System.out.println(zi.num);//200  如果把子类的num注释掉,则是100

        //首先看这个方法是属于子类的,优先在子类当中找num,没有再找父类的num
        zi.methodZi();//200  如果把子类的num注释掉,则是100

        //首先看这个方法是父类的,调用的就是父类的num
        zi.methodFu();//100
    }
}

局部变量、子类成员变量、父类成员变量重名:

/*
局部变量:直接写变量
子类成员变量:this.成员变量
父类成员变量:super.成员变量
*/

//父类
public class Fu {
    int num = 100;
}

//子类
public class Zi extends Fu {
    int num = 200;

    public void methodZi() {
        int num = 60;//直接写
        System.out.println("局部变量:" + num);//60
        System.out.println("子类成员变量:" + this.num);//200
        System.out.println("父类成员变量:" + super.num);//100
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.methodZi();
    }
}

//结果:
局部变量:60
子类成员变量:200
父类成员变量:100

9.3 继承后的特点-成员方法

成员方法不重名

//父类
public class Fu {
    
    public void methodFu(){
        System.out.println("父类方法执行了!");
    }
}

//子类
public class Zi extends Fu {

    public void methodZi() {
        System.out.println("子类方法执行了!");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Zi zi=new Zi();
        zi.methodFu();//父类方法执行了!
        zi.methodZi();//子类方法执行了!
    }
}

成员方法重名

创建的对象是谁就优先用谁,如果没有则向上找。

//父类
public class Fu {

    public void method(){
        System.out.println("父类方法执行了!");
    }
}

//子类
public class Zi extends Fu {

    public void method() {
        System.out.println("子类方法执行了!");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Zi zi=new Zi();
        //创建的对象是谁就优先用谁
        zi.method();//子类方法执行了!
    }
}

注意事项:无论是成员方法还是成员变量,如果没有都是向上找,绝对不会向下找子类的。

9.4 继承中成员方法的覆盖重写(Override)

重写概念:在继承关系当中,方法的名称一样,参数列表也一样。

重写(Override):方法的名称一样,参数列表【也一样】。课本一般写”重写“,老师建议”覆盖“。

重载(Overload):方法的名称一样,参数列表【不一样】。

成员方法覆盖特点:如果创建的是子类对象,则优先用子类方法。

覆盖注意事项:

  • 必须保证父子类当中方法名称、参数列表一模一样。
  • @Override 这个注解写在方法上面,可以用来检测是不是有效的正确覆盖。【非必须写,建议】
  • 子类方法的返回值必须【小于等于】父类方法的返回值范围。
  • 子类方法的权限必须【大于等于】父类方法的权限修饰符。

演示@Override 用法

//父类
public class Fu {

    public void method(){
        System.out.println("父类方法执行了!");
    }
}

//子类
public class Zi extends Fu {

    @Override
    public void method() {
        System.out.println("子类方法执行了!");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Zi zi=new Zi();
        //创建的对象是谁就优先用谁
        zi.method();//子类方法执行了!
    }
}

了解:java.lang.Object类是所有类的公共最高父类(祖宗),例如:java.lang.String就是Object的子类。

public class Fu {

    public Object method() {//父类范围最大,Object、String对调试试
        return null;
    }
}

public class Zi extends Fu {

    @Override
    public Object method() {//或Object改为String是可以的,因为比它小。
        return null;
    }
}

修饰符权限大小:public > protected > default(什么都不写) > private

public class Fu {

    public Object method() {
        return null;
    }
}

public class Zi extends Fu {

    @Override
    public String method() {//改为private试试
        return null;
    }
}

一般情况下,绝大部分情况下,99%,我们写父类和子类方法的返回值、权限都是相等关系。

9.5 继承中方法的覆盖重写_应用场景

以一个手机生产商为例,假设它的生产线、技术是可以继承沿用的,即生产新手机可以利用旧手机的流水线。

image-20210530211737223

已经投入使用的类,在生产环境中有很多类(代码)在使用它,改动它会导致整个系统崩溃,但通过继承的覆盖来添加改动就可以,这就是继承存在的最大意义。

public class oldPhone {
    public void call(){
        System.out.println("打电话");
    }
    public void send(){
        System.out.println("发短信");
    }
    public void show(){
        System.out.println("显示号码");
    }
}

public class newPhone extends  oldPhone {
    @Override
    public void show(){
        //System.out.println("显示号码"); 这条优化掉,不写父类已有的,因为父类有很多的话再写一遍不太现实
        super.show();//把父类的show方法重复利用
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

public class Use {
    public static void main(String[] args) {
        oldPhone oldphone=new oldPhone();
        oldphone.call();
        oldphone.send();
        oldphone.show();
        System.out.println("-------------------------------------");
        newPhone newphone = new newPhone();
        newphone.call();
        newphone.send();
        newphone.show();
    }
}

9.6 继承中构造方法的访问特点

先看一个案例:

public class Fu {

    public Fu() {
        System.out.println("父类构造方法执行了");
    }
}

public class Zi extends Fu {

    public Zi() {
        System.out.println("子类构造方法执行了");
    }
}

public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();//先打印父类构造方法后打印子类构造方法
    }
}

子类构造方法当中有一个默认隐含的“super()”,所以一定是先调用的父类构造,后执行的子类构造。(赠送)

public class Fu {

    public Fu() {
        System.out.println("父类构造方法执行了");
    }
}

public class Zi extends Fu {
    
    public Zi() {
        super();//默认有的,不用写。意思是调用父类的无参数构造方法。
        System.out.println("子类构造方法执行了");
    }
}

public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();//先打印父类构造方法后打印子类构造方法
    }
}

调用父类的有参数构造方法

super()必须写在子类构造方法的第一个语句,也不能写两个super,即不可能同时调用两个父类的构造方法

public class Fu {

    public Fu(int num) {
        System.out.println("父类构造方法执行了");
    }
}

public class Zi extends Fu {
    
    public Zi() {
        super();//报错,父类没有无参数构造方法
        super(10);//正确
        System.out.println("子类构造方法执行了");
    }
}

public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();//先打印父类构造方法后打印子类构造方法
    }
}

子类必须调用父类构造方法,不写也会强制赠送一个super()给你。

9.7 super关键字的三种用法

super :代表父类的存储空间标识(地址)。

1.在子类的成员方法中,访问父类的成员变量。

public class Fu {
    int num = 10;
}

public class Zi extends Fu {
    int num = 20;
    public void methodZi(){
        System.out.println(num);//20
        System.out.println(super.num);//10   在子类的成员方法中,访问父类的成员变量。
    }
}

public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.methodZi();//调用
    }
}

2.在子类的成员方法中,访问父类的成员方法。

public class Fu {
    int num = 10;

    public void method(){
        System.out.println("父类方法");
    }
}

public class Zi extends Fu {
    int num=20;

    public void methodZi(){
        System.out.println(num);//20
        System.out.println(super.num);//10   在子类的成员方法中,访问父类的成员变量。
    }
    public void method(){
        super.method();//在子类的成员方法中,访问父类的成员方法。
        System.out.println("子类方法");
    }
}

public class Use {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.methodZi();
        zi.method();//调用
    }
}

3.在子类的构造方法中,访问父类的构造方法。(前面已讲)

9.8 this关键字的三种用法

super用来访问父类内容,而this用来访问本类内容。

1.在本类的成员方法中,访问本类的成员变量。

public class Fu {
    int num = 10;
}

public class Zi extends Fu {
    int num = 20;

    public void methodZi(){
        int num = 30;
        System.out.println(num);//30 局部变量
        System.out.println(this.num);//20 本类成员变量
        System.out.println(super.num);//10 父类成员变量。
    }
}

2.在本类的成员方法中,访问本类的另外一个成员方法。

public class Zi extends Fu {
    int num=20;

    public void methodZi(){
        int num=30;
        System.out.println(num);//30 局部变量
        System.out.println(this.num);//20 本类成员变量
        System.out.println(super.num);//10 父类成员变量。
    }
    
    public void method(){
        this.methodZi();//可以不带this,但是强烈推荐,起到强调,易读的效果
    }
}

3.在本类的构造方法中,访问本类的另外一个构造方法。注意:this()必须是第一个语句。因此super不能跟它同时使用(super不再赠送)。也不能写两个this()。

public class Zi extends Fu {
    
    public Zi(){
        this(10);//无参调用有参
    }
    
    public Zi(int a){

    }
}

9.9 Java继承的三个特点

  1. Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //正确
class C extends A,B...{} //错误,两个父类导致子类不懂听谁的,即成员变量、方法不懂用谁的好。
  1. Java支持多层继承(继承体系)。
class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。

  1. 子类和父类是一种相对的概念。

练习:

10. 抽象类

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。

我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类

如下图:图形父类中计算面积的方法,因形状太多写不了,只能是抽象的方法类中计算面积的方法,因形状太多写不了,只能是抽象的方法。

动物吃的方法也不够具体,也只能是抽象的方法。

image-20210531123847036

10.1 抽象类定义

抽象方法定义

修饰符 abstract 返回值类型 方法名 (参数列表);

//示例:
public abstract void run();//注意void不是固定的

抽象类定义

如果一个类包含抽象方法,那么该类必须是抽象类。

abstract class 类名字 {
}

//示例:
public abstract class Animal {
    public abstract void run();
}

完整的示例:

public abstract class Animal {
    //抽象方法
    public abstract void eat();

    //普通方法
    public void normalMethod() {

    }
}

10.2 抽象的使用

  1. 不能直接创建抽象类对象。
  2. 必须用一个子类来继承抽象父类。
  3. 子类必须覆盖重写抽象父类当中【所有的】抽象方法。覆盖重写(实现):在子类当中去掉关键字abstract,补上{}当中的方法体。
//抽象类
public abstract class Animal {
    //抽象方法
    public abstract void eat();
    public abstract void walk();//开始先不写,后面再加
    //普通方法
    public void normalMethod() {

    }
}

//子类
public class Cat extends Animal {

    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.walk()//未写这行时提示:Cat不是抽象的, 并且未覆盖Animal中的抽象方法walk()
    }
}

练习:

11. 接口

接口就是一种公共的规范标准,谁都可以用,提高了便利。

image-20210531195204830

11.1 接口的定义

在Java当中,接口就是多个类的公共规范,是一种引用类型

接口是方法的集合,它的内部主要就是封装了方法 ,包含:

  • 常量

  • 抽象方法(JDK 7及以前 )

  • 默认方法(JDK8)

  • 静态方法(JDK8)

  • 私有方法(JDK9)

//说明:interface表示接口,它编译后的字节码仍然是.class
public interface 接口名称 {
    // 抽象方法
    // 默认方法
    // 静态方法
    // 私有方法
}

如果是JDK7,那么接口中可以包含的内容有:

  • 常量
  • 抽象方法

如果是JDK8,那么接口中可以包含的内容有:

  • 常量
  • 抽象方法
  • 默认方法
  • 静态方法

如果是JDK9,那么接口中可以包含的内容有:

  • 常量
  • 抽象方法
  • 默认方法
  • 静态方法
  • 私有方法

11.2 接口的抽象方法定义(JDK7)

具体操作

New-->Java Class

<img src="Java精讲精练.assets/image-20210531201129882.png" alt="image-20210531201129882" style="zoom:80%;" />

/*
在任何版本的JDK当中,接口都能定义抽象方法。
接口当中的抽象方法必须以public abstract开头,但这两个修饰符可以不写,不推荐。
 */
public interface MyInterface {
    //抽象方法,以public abstract开头,推荐写法。注意void是返回类型,不是总是void的,比如是String、int等
    public abstract void methodAbs1();
    //这也是抽象方法
    public void methodAbs2();
    //这也是抽象方法
    abstract void methodAbs3();
    //这也是抽象方法
    void methodAbs4();
}

11.3 接口的抽象方法使用

  • 接口不能直接使用,必须有一个“实现类”来实现该接口。
  • 接口的实现类必须覆盖重写接口中【所有的】抽象方法。
  • 创建实现类的对象使用

实现类的格式:

class 类名 implements 接口名 {
    //重写接口中的所有抽象方法【必须】
}

//示例:
//实现类的命名建议,在接口上加Impl,增强可读性
public class MyInterfaceImpl implements MyInterface{
    
}

一个完整的示例:

先写:

public class MyInterfaceImpl implements MyInterface{
    
}

在{}中:alt+insert 生成

image-20210531204920907

<img src="Java精讲精练.assets/image-20210531205010395.png" alt="image-20210531205010395" style="zoom:67%;" />

public class MyInterfaceImpl implements MyInterface{
    @Override
    public void methodAbs1() {
        
    }

    @Override
    public void methodAbs2() {

    }

    @Override
    public void methodAbs3() {

    }

    @Override
    public void methodAbs4() {

    }
}

再补充完各个抽象方法

public class MyInterfaceImpl implements MyInterface{
    @Override
    public void methodAbs1() {
        System.out.println("这是第一个方法");
    }

    @Override
    public void methodAbs2() {
        System.out.println("这是第二个方法");
    }

    @Override
    public void methodAbs3() {
        System.out.println("这是第三个方法");
    }

    @Override
    public void methodAbs4() {
        System.out.println("这是第四个方法");
    }
}

实现类的使用(注意:接口不能new):

public class Use {
    public static void main(String[] args) {
        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.methodAbs1();
        impl.methodAbs2();
        impl.methodAbs3();
        impl.methodAbs4();
    }
}

11.4 接口的默认方法定义(JDK8)

格式:

public default 返回值类型 方法名称(参数列表){
    方法体;
}

//示例:
public default void eat(){
    方法体;
}

为什么提出默认方法?是为了解决接口升级(增加)的问题。

接口增加后的问题演示:

原先有一个接口,有两个实现类在使用它,这两个实现类使用广泛。

//接口
public interface MyInterfaceDefault {
    public abstract void methodAbs1();
}
//实现类1
public class MyInterfaceDefaultA implements MyInterfaceDefault{
    @Override
    public void methodAbs1(){
        System.out.println("实现了抽象方法A");
    }
}
//实现类2
public class MyInterfaceDefaultB implements MyInterfaceDefault{
    @Override
    public void methodAbs1(){
        System.out.println("实现了抽象方法B");
    }
}

当增加一个接口,如:

//接口
public interface MyInterfaceDefault {
    public abstract void methodAbs1();
    public abstract void methodAbs2();
}

会导致两个实现类报错,你得把methodAbs2()实现后才不会报错,但这个两个实现类使用广泛,我不想改动它,以免出现未知错误。

这就引入了默认方法,因为它即可以直接调用,也可以重写。 即继承、重写二选一。

public interface MyInterfaceDefault {
    public abstract void methodAbs1();
    //添加默认方法,public可以不写,但是不能改成别的
    public default void methodDefault(){
        System.out.println("接口新添加的默认方法");
    }
}

实现类可以不重写默认方法

//实现类1,不重写默认方法
public class MyInterfaceDefaultA implements MyInterfaceDefault{
    @Override
    public void methodAbs1(){
        System.out.println("实现了抽象方法A");
    }
}
//实现类2,重写默认方法(如果默认方法有重名的,必须重写一次。)
public class MyInterfaceDefaultB implements MyInterfaceDefault{
    @Override
    public void methodAbs1(){
        System.out.println("实现了抽象方法B");
    }
    
    @Override
    public void methodDefault(){
        System.out.println("实现类B重写了接口的默认方法");
    }
}

使用:

public class Use {
    public static void main(String[] args) {
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
        a.methodDefault();//接口新添加的默认方法
        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodDefault();//实现类B重写了接口的默认方法
    }
}

11.5 接口的静态方法定义(JDK8)

格式:

public static 返回值类型 方法名称(参数列表){
    方法体;
}

//示例:
public static void eat(){
    方法体;
}
//接口
public interface MyInterfaceStatic {
    //public可不写,不推荐
    public static void methodStatic(){
        System.out.println("接口的静态方法");
    }
}
//实现类
public class MyInterfaceStaticImpl implements MyInterfaceStatic{

}

使用:实现类不能直接调用接口的静态方法,因为实际运用时,不止写一个接口的静态方法,如果静态方法名称一样,则出现冲突。

public class Use {
    public static void main(String[] args) {
        MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
        impl.methodStatic();//提示错误。
        
        //通过接口名称直接调用静态方法,上面的3、4行代码都没有必要写,因为静态跟对象没关系,直接使用
        MyInterfaceStatic.methodStatic();
    }
}

11.6 接口的私有方法定义(JDK9)

如果接口中有两个方法的内容重复内容太多了,我们就抽取共性。如下:

public interface MyInterface {
    public default void methodDefault1(){
        System.out.println("默认方法1");
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
    public default void methodDefault2(){
        System.out.println("默认方法2");
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

发有重复的方法内容AAA、BBB、CCC,按普通的方法抽取是可以的:

public interface MyInterface {
    public default void methodDefault1(){
        System.out.println("默认方法1");
        methodCommon();
    }
    public default void methodDefault2(){
        System.out.println("默认方法2");
        methodCommon();
    }
    public default void methodCommon(){
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

但是这种提取方法methodCommon()实现类可以访问,没有什么防止重复意义。

我们抽取一个共有方法,是用来解决两个默认方法之间重复代码的问题。这个共有方法不应该让实现类使用,应该私有化。你实现类不需要知道那么多,因为知道的多就背得多,记得多。

上面的methodCommon()可以当做普通的私有方法,不是完全私有。

从JDK 9开始,接口当中允许定义私有方法。

  1. 普通私有方法,解决多个默认方法之间重复代码问题 格式:
private 返回类型 方法名称(参数列表){
    方法体;
}
  1. 静态私有方法,解决多个静态方法之间重复代码问题。
private static 返回类型 方法名称(参数列表){
    方法体;
}

普通私有方法

public interface MyInterface {
    public default void methodDefault1(){
        System.out.println("默认方法1");
        methodCommon();
    }
    public default void methodDefault2(){
        System.out.println("默认方法2");
        methodCommon();
    }
    private void methodCommon(){
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

静态私有方法

public interface MyInterface {
    public static void methodDefault1(){
        System.out.println("静态方法1");
        methodCommon();
    }
    public static void methodDefault2(){
        System.out.println("静态方法2");
        methodCommon();
    }
    private static void methodCommon(){//不写public static,相当于多了一个静态方法,得记!
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

私有方法,在使用时,接口不能调用,因为是private

public class Use {
    public static void main(String[] args) {
        MyInterface.methodDefault1();//正确
        MyInterface.methodDefault2();//正确
        MyInterface.methodCommon();//错误
    }
}

11.7 接口的常量定义和使用

接口可以定义“成员变量”,使用public static final三个关键字进行修饰。

常量必须赋值。

常量推荐使用全大写加下划线

public interface MyInterface {
    //常量,一旦赋值就不能修改,跟对象没关系;可以不写public static final
    public static final int MY_NUM = 10;//final意思是不可改变
}
public class Use {
    public static void main(String[] args) {
        System.out.println(MyInterface.MY_NUM);//10
    }
}

练习,写一个接口MyInterface,同时有常量、抽象方法、默认方法、静态方法、私有方法,并定义一个实现类、使用类。

12. 多态

描述的是对象的多种形态。

<img src="Java精讲精练.assets/image-20210601000735526.png" alt="image-20210601000735526" style="zoom: 67%;" />

12.1 多态的格式与使用

对象多态一句话描述:父类引用指向子类对象。左父右子。

父类名称 父类对象名 = new 子类名称();

//或者:
接口名称 接口对象名 = new 实现类名称();

演示多态:一个学生(子类),被当作人类(父类)来看待是没有问题的,反之也一样。

public class Fu {
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFu(){
        System.out.println("父类特有方法");
    }
}
public class Zi extends Fu {
    //重写父类方法
    @Override
    public void method(){
        System.out.println("子类方法");
    }
}
public class Use {
    public static void main(String[] args) {
        //左父右子,多态写法,obj指向右边
        Fu obj = new Zi();
        obj.method();//子类方法
        obj.methodFu();//父类特有方法
    }
}

12.2 多态中成员变量的使用特点

直接通过对象名称访问成员变量。

//父类
public class Fu {
    int num = 10;
}

//子类
public class Zi extends Fu {
    int num = 20;
    int age = 18;//变量不能重写,所以是子类特有的,父类用不了
}

//多态使用
public class Use {
    public static void main(String[] args) {
        //左父右子,多态写法,obj指向右边
        Fu obj = new Zi();
        System.out.println(obj.num);//10  看等号左边是谁,优先用谁,没有则向上找(父子相对)
        System.out.println(obj.age);//错误,因为只能向上查找,找不了子类的成员变量
    }
}

间接通过成员方法访问变量:如果有重写,则用重写的。

//无重写
//父类
public class Fu {
    int num = 10;
    //增加一个方法
    public void shownum(){
        System.out.println(num);
    }
}

//子类
public class Zi extends Fu {
    int num = 20;
}

//使用
public class Use {
    public static void main(String[] args) {
        //左父右子,多态写法,obj指向右边
        Fu obj = new Zi();
        System.out.println(obj.num);//10  成员变量是优先看等号左边的,不看右边
        obj.shownum();//子类没有重写,结果为10
    }
}
//有重写
//父类
public class Fu {
    int num = 10;
    //增加一个方法
    public void shownum(){
        System.out.println(num);
    }
}

//子类
public class Zi extends Fu {
    int num = 20;
    @Override
    public void shownum(){
        System.out.println(num);
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        //左父右子,多态写法,obj指向右边
        Fu obj = new Zi();
        System.out.println(obj.num);//10  成员变量是优先看等号左边的,不看右边
        obj.shownum();//子类有重写,结果为20
    }
}

12.3 多态中成员方法的使用特点

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。

//父类
public class Fu {
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFu(){
        System.out.println("父类特有方法");
    }
}
//子类
public class Zi extends Fu {
    @Override
    public void method(){
        System.out.println("子类方法");
    }
    public void methodZi(){
        System.out.println("子类特有方法");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Fu obj = new Zi();//多态
        obj.method();//父子都有,优先用子
        obj.methodFu();//子类没有,父类有,向上查找
        //obj.methodZi();父类没有这个方法,编译器找不到,错误
    }
}

多态中成员变量只能使用父类自己的,成员方法是反过来,优先使用子类的,没有才用自己的,但首要条件也要自己有方法才行。

12.4 使用多态的好处

多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

image-20210606215612562

如图,Teacher讲师、Assistant助教、Employee员工。

在老板眼里,不管你是什么岗位,他只关心你工作不工作,你工作了才发钱。因此你使用多态时,左侧统一都是员工,右侧就根据工作需要new谁,叫谁去干活。

用了多态,代码更直观,父子一眼看出,便于归类,编写简便,扩展性好。

12.5 对象的向上转型

image-20210606224244753

public abstract class Animal {
    public abstract void eat();
}

public class Cat extends Animal{
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

public class Use {
    public static void main(String[] args) {
        //对象向上转型,就是父类引用指向子类对象
        Animal animal = new Cat();
        animal.eat();//猫吃鱼
    }
}

缺点:子类独有的方法,也即父类没有的方法,不能调用。

12.6 对象的向下转型

image-20210606233032974

//动物类
public abstract class Animal {
    public abstract void eat();
}

//猫类
public class Cat extends Animal{
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
    //子类特有方法
    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}

//狗类
public class Dog extends Animal{
    @Override
    public void eat(){
        System.out.println("狗吃骨头");
    }
    public void watchHouse(){
        System.out.println("狗看家");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        //对象向上转型,就是父类引用指向子类对象
        Animal animal = new Cat();//本来创建的是一只猫
        animal.eat();//猫吃鱼
        //animal.catchMouse();错误

        //向下转型,进行“还原”动作
        Cat cat = (Cat) animal;
        cat.catchMouse();//猫抓老鼠
        //错误的向下转型
        //本来new的时候是一个猫,现在非要当成狗。
        //Dog dog = (Dog) animal; 编译不会报错,但运行会,提示类型转换异常!
    }
}

12.7 用instanceof关键字进行类型判断

//得到一个boolean值结果,判断前面的对象能不能当做后面类型的实例。
对象 instanceof 类名称
public class Use {
    public static void main(String[] args) {
        //对象向上转型,就是父类引用指向子类对象
        Animal animal = new Cat();//本来创建的是一只猫
        animal.eat();//猫吃鱼

        //如果希望调用子类特有方法,需要向下转型
        //判断父类引用animal本来是不是Dog
        if(animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }
        //判断父类引用animal本来是不是Cat
        if(animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }
    }
}

前面的向上、向下转型是不是多余?我直接用子类不香吗?

//转型可以满足子类多变的场景
public class Use {
    public static void main(String[] args) {
        //对象向上转型,就是父类引用指向子类对象
        Animal animal = new Cat();//本来创建的是一只猫
        animal.eat();//猫吃鱼

        //如果希望调用子类特有方法,需要向下转型
        //判断父类引用animal本来是不是Dog
        if(animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }
        //判断父类引用animal本来是不是Cat
        if(animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }

        giveMeAPet(new Dog());//想换什么动物都可以

    }

    public static void giveMeAPet(Animal animal){
        if(animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }
        if(animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }
    }
}

13. final关键字

final:最终的、不可改变的。

常见4种用法:

  • 修饰一个类:被修饰的类,不能被继承。
  • 修饰一个方法:被修饰的方法,不能被重写。
  • 修饰一个局部变量:被修饰的变量,不能被重新赋值。
  • 修饰一个成员变量:被修饰的变量,不能被重新赋值。

修饰一个类

public final class MyClass {
}

//定义一个子类来测试能否被继承
public class MySubClass extends MyClass{//编译器不给通过
}

修饰一个方法

public class MyClass {
    public final void method(){
        System.out.println("父类方法执行!");
    }
}

//以下错误,不能重写
public class MySubClass extends MyClass{
    @Override
    public void method(){
        System.out.println("子类方法执行!");
    }
}

对于类、方法来说,abstract和final不能同时使用,因为abstract是必须重写,而final不能重写,是矛盾的。

修饰一个局部变量

public  class Animal {
    String name;
}

public class Use {
    public static void main(String[] args) {
        int num1 = 10;
        System.out.println(num1);//10
        num1 = 20;
        System.out.println(num1);//20
        final int num2 = 200;//final修饰后,只能赋值一次且终身不变。
        System.out.println(num2);
        //num2 = 250;无法为最终变量num2分配值
        //num2 = 200;无法为最终变量num2分配值

        //保证只有一次赋值
        final int num3;
        num3 = 300;

        /*
        对于基本类型来说,不可变说的是变量当中的数据不可改变。
        对于引用类型来说,不可变说的是变量当中的地址值不可改变。
        */
        //注意是地址值不变,但内容可变
        final Animal animal = new Animal();
        animal.name = "猫";
        System.out.println(animal.name);//猫
        animal.name = "狗";
        System.out.println(animal.name);//狗
        //把另外的对象赋值给animal错误
        Animal animal1 =new Animal();
        //animal= animal1; 无法为最终变量animal分配值
    }
}

修饰一个成员变量

由于成员变量具有默认值,因此用了final之后必须手动赋值,不会再给默认值了。

public class Animal {
    //以下两种方法二选一使用
    final String name = "猫";//直接赋值
    final String color;//通过构造方法赋值

    //两种构造方法都可以,看你调用谁
    public Animal(){
        color="黄色";
    }
    public Animal(String color) {
        this.color = color;
    }   
}

14. 内部类

什么是内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

例如:身体和心脏的关系、汽车和发动机的关系,两者都是缺一不可。

分类:

  • 成员内部类
  • 局部内部类(含匿名内部类)

14.1 成员内部类的定义

class 外部类 {
    class 内部类{
    }
}

特点:

  • 内部使用外部的,可以随意访问。(类似继承)
  • 外部使用内部,需要内部类对象。
public class Car {
    private String name;//注意我已经设置为private了
    //Car的成员方法
    public void methodCar() {
        System.out.println("外部类的方法");
    }

    public class Engine {//Car的成员内部类

        //内部类的方法
        public void methodEng() {
            System.out.println("发动机在转");
            System.out.println(name);//内部类可以使用外部类的东西。
        }
    }
}

14.2 成员内部类的使用

两种方式:

  • 间接方式:在外部类的方法当中,使用内部类;然后只调用外部类的方法。
public class Car {
    private String name;
    //Car的成员方法
    public void methodCar() {
        System.out.println("外部类的方法");
        //通过内部类的对象来调用
        Engine engine = new Engine();
        engine.methodEng();
    }

    public class Engine {//Car的成员内部类

        //内部类的方法
        public void methodEng() {
            System.out.println("发动机在转");
            System.out.println(name);
        }
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Car car = new Car();//外部类的对象
        car.methodCar();//外部类的成员方法
    }
}
  • 直接方式:用公式
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
public class Use {
    public static void main(String[] args) {
        Car.Engine engine = new Car().new Engine();//创建内部类对象
        engine.methodEng();//内部类方法
    }
}

14.3 内部类的同名变量访问

public class Outer {
    int num =10 ;//外部类的成员变量
    public class Inner{
        int num=20;//内部类的成员变量
        public void methodInner(){
            int num=30;//内部类的成员方法的局部变量
            System.out.println(num);//局部变量
            System.out.println(this.num);//内部类的成员变量
            //System.out.println(super.num);错误,不是继承关系,他们的祖类都是Object类
            System.out.println(Outer.this.num);//外部类名.this.外部类成员变量名
        }
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().new Inner();
        inner.methodInner();
    }
}

14.4 局部内部类定义

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。

局部理解:只有在当前方法当中的才能使用,出到方法外面就不能使用。

public class Outer {
    public void methodOuter() {
        class Inner {//局部内部类。不写public,可以写上试下
            int num = 10;

            public void methodInner() {
                System.out.println(num);//10
            }
        }
        //怎么使用局部内部类。在外部内的成员方法中new
        Inner inner=new Inner();
        inner.methodInner();
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.methodOuter();//10
    }
}

14.5 局部内部类的final问题

/*
为什么用final?
1.new出来的内部对象inner在堆内存当中
2.局部变量是跟着方法走的,在栈内存当中。
3.方法运行结束之后,立刻出栈,局部变量就会立刻消失。
4.但是new出来的inner对象会在堆当中持续存在,直到垃圾回收消失。
因此new出来的inner对象只取一次num的值保存,消失也不影响。如果你num多变,则在编译器这里就不允许。
 */
public class Outer {
    public void methodOuter() {
        int num = 10;//所在方法的局部变量,可加final,也可不加final;但加了才能保证num不能改变。

        class Inner {//局部内部类。

            public void methodInner() {
                System.out.println(num);//要想访问所在方法的局部变量,它必须不能改变。即num不能再赋值,最好加上final
            }
        }
    }
}

14.6 匿名内部类

回忆一个正常的接口用法

//接口
public interface MyInterface {
    public  abstract void method();
}

//实现类
public class MyInterfaceImpl implements MyInterface{
    @Override
    public void method(){
        System.out.println("本实现类覆盖重写了抽象方法");
    }
}

//使用
public class Use {
    public static void main(String[] args) {
        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.method();
    }
}

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,则可以省略该的定义,改为使用【匿名内部类】。

//接口的匿名内部类定义
接口名称 对象名 = new 接口名称(){
    覆盖重写接口中所有抽象方法
};
public class Use {
    public static void main(String[] args) {
        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.method();//本实现类覆盖重写了抽象方法
        //实现类如果只使用唯一的一次,则可以使用匿名内部类
        MyInterface obj = new MyInterface() {//匿名内部类,没有对象名称
            @Override
            public void method() {
                System.out.println("匿名内部类实现的方法");
            }
        };
        obj.method();//匿名内部类实现的方法   从结果看MyInterfaceImpl没必要写了,这是匿名的好处。
    }
}

15. 引用类型用法总结

实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步 去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类 型也是可以的。

15.1 类作为成员变量类型

首先理解:不管什么系统,升级更新是很常见的、必须的,因此写的代码必须具体良好的扩展性。

初步定义一个类Role(游戏角色)时,代码如下 :

public class Role {
    private int id; // 角色id
    private int blood; // 生命值
    private String name; // 角色名称
}

如果我们继续丰富这个类的定义,给 Role 增加武器,穿戴装备等属性,我们将如何编写呢?

public class Weapon {
    private String name; // 武器名称
    private int hurt; // 伤害值,有钱就有伤害值
    //省略alt+insert生成的构造方法和get set
}

定义穿戴盔甲类,将增加防御能力,也就是提升生命值:

public class Armour {
    private String name;// 装备名称
    private int protect;// 防御值,花钱越多防御值越高
    //省略alt+insert生成的构造方法和get set
}

Role最终成型:

public class Role {
    private int id; // 角色id
    private int blood; // 生命值
    private String name; // 角色名称
    //添加武器属性
    Weapon wp;
    //添加盔甲属性
    Armour ar;

    //alt+insert
    public Role() {
    }

    public Role(int id, int blood, String name) {//不设置wp、ar
        this.id = id;
        this.blood = blood;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getBlood() {
        return blood;
    }

    public void setBlood(int blood) {
        this.blood = blood;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Weapon getWp() {
        return wp;
    }

    public void setWp(Weapon wp) {
        this.wp = wp;
    }

    public Armour getAr() {
        return ar;
    }

    public void setAr(Armour ar) {
        this.ar = ar;
    }

    //攻击方法
    public void attack() {
        System.out.println(this.name + "使用" + wp.getName() + ",造成对方" + wp.getHurt() + "点伤害");
    }

    // 穿戴盔甲
    public void wear() {
        //增加防御,就是增加blood值
        this.blood += ar.getProtect();
        System.out.println(this.name + "穿上" + ar.getName() + ", 生命值增加" + ar.getProtect());
    }

    //生命值
    public void life() {
        int life = this.blood;
        System.out.println(this.name +"还有多少血:"+life);
    }
}

使用:

public class Use {
    public static void main(String[] args) {
        // 创建Weapon 对象
        Weapon wp = new Weapon("屠龙刀" , 9999);
        // 创建Armour 对象
        Armour ar = new Armour("麒麟甲",10000);

        // 创建英雄
        Role r = new Role(1,20000,"盖伦");
        //设置武器属性
        r.setWp(wp);
        //设置盔甲属性
        r.setAr(ar);
        //攻击效果
        r.attack();
        //穿戴盔甲效果
        r.wear();
        //生命值还有多少
        r.life();
    }
}
-----------------------------------------
结果:
盖伦使用屠龙刀,造成对方9999点伤害
盖伦穿上麒麟甲, 生命值增加10000
盖伦还有多少血:30000

思考:武器和盔甲为什么不放在Role类里?

15.2 interface作为成员变量

接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在 Role 中,可以增加接口作为成员变量,来设置不同的技能。

定义接口,为我的英雄,新增、更新一个“法术攻击”能力:

public interface FaShuSkill {
    //法术攻击
    public abstract void faShuAttack();
}

Role增加代码:

    //增加成员变量:法术攻击
    private FaShuSkill fs;//不用做实现类
    //alt+insert
    public FaShuSkill getFs() {
        return fs;
    }

    public void setFs(FaShuSkill fs) {
        this.fs = fs;
    }
    public void faShuSkillAttack(){
        System.out.print(this.name +"发动法术攻击:");
        fs.faShuAttack();
        System.out.println("攻击完毕");
    }

使用:Use新增

        //新增
        //r.setFs(FaShuSkill fs);把FaShuSkill fs写成匿名内部类
        r.setFs(new FaShuSkill() {
            @Override
            public void faShuAttack() {
                System.out.println("纵横天下");
            }
        });
        //发动法术攻击
        r.faShuSkillAttack();

        //更换技能
        r.setFs(new FaShuSkill() {
            @Override
            public void faShuAttack() {
                System.out.println("逆转乾坤");
            }
        });
        //发动法术攻击
        r.faShuSkillAttack();
结果:
盖伦发动法术攻击:纵横天下
攻击完毕
盖伦发动法术攻击:逆转乾坤
攻击完毕

我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。

16. 常用API第二部分

16.1 Object类

16.1.1 概述

java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。

如果一个类没有特别指定父类, 那么默认则继承自Object类。例如:

public class MyClass /*extends Object*/ {
      // ...
}

根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的2个:

  • public String toString():返回该对象的字符串表示。
  • public boolean equals(Object obj):指示其他某个对象是否与此对象“相等”。

16.1.2 toString方法

方法摘要

  • public String toString():返回该对象的字符串表示。

toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。

由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。

覆盖重写

如果不希望使用toString方法的默认行为,则可以对它进行覆盖重写。例如自定义的Person类:

public class Person {  
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // 省略构造器与Getter Setter
}

在IntelliJ IDEA中,可以点击Code菜单中的Generate...,也可以使用快捷键alt+insert,点击toString()选项。选择需要包含的成员变量并确定。

小贴士: 在我们直接使用输出语句输出对象名的时候,其实通过该对象调用了其toString()方法。

16.1.3 equals方法

方法摘要

  • public boolean equals(Object obj):指示其他某个对象是否与此对象“相等”。

调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。这里的“相同”有默认和自定义两种方式。

默认地址比较

如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false。

对象内容比较

如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。例如:

import java.util.Objects;

public class Person {    
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object o) {
        // 如果对象地址一样,则认为相同
        if (this == o)
            return true;
        // 如果参数为空,或者类型信息不一样,则认为不同
        if (o == null || getClass() != o.getClass())
            return false;
        // 转换为当前类型
        Person person = (Person) o;
        // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果
        return age == person.age && Objects.equals(name, person.name);
    }
}

这段代码充分考虑了对象为空、类型一致等问题,但方法内容并不唯一。大多数IDE都可以自动生成equals方法的代码内容。在IntelliJ IDEA中,可以使用Code菜单中的Generate…选项,也可以使用快捷键alt+insert,并选择equals() and hashCode()进行自动代码生成。

tips:Object类当中的hashCode等其他方法,今后学习。

16.1.4 Objects类

在刚才IDEA自动重写equals代码中,使用到了java.util.Objects类,那么这个类是什么呢?

JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:

  • public static boolean equals(Object a, Object b):判断两个对象是否相等。

我们可以查看一下源码,学习一下:

public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

16.2 日期时间类

16.2.1 Date类

概述

java.util.Date类 表示特定的瞬间,精确到毫秒。(1000毫秒=1秒)

继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,但是其中有未过时的构造函数可以把毫秒值转成日期对象。

  • public Date():分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
  • public Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。

tips: 由于我们处于东八区,所以我们的基准时间为1970年1月1日8时0分0秒。

简单来说:使用无参构造,可以自动设置当前系统时间的毫秒时刻;指定long类型的构造参数,可以自定义毫秒时刻。例如:

import java.util.Date;

public class Demo01Date {
    public static void main(String[] args) {
        // 创建日期对象,把当前的时间
        System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2018
        // 创建日期对象,把当前的毫秒值转成日期对象
        System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
    }
}

tips:在使用println方法时,会自动调用Date类中的toString方法。Date类对Object类中的toString方法进行了覆盖重写,所以结果为指定格式的字符串。

常用方法

Date类中的多数方法已经过时,常用的方法有:

  • public long getTime() 把日期对象转换成对应的时间毫秒值。

16.2.2 DateFormat类

java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

  • 格式化:按照指定的格式,从Date对象转换为String对象。
  • 解析:按照指定的格式,从String对象转换为Date对象。

构造方法

由于DateFormat为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:

  • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。

参数pattern是一个字符串,代表日期时间的自定义格式。

格式规则

常用的格式规则为:

标识字母(区分大小写) 含义
y
M
d
H
m
s

备注:更详细的格式规则,可以参考SimpleDateFormat类的API文档0。

创建SimpleDateFormat对象的代码如:

import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class Demo02SimpleDateFormat {
    public static void main(String[] args) {
        // 对应的日期格式如:2018-01-16 15:06:38
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }    
}

常用方法

DateFormat类的常用方法有:

  • public String format(Date date):将Date对象格式化为字符串。
  • public Date parse(String source):将字符串解析为Date对象。

format方法

使用format方法的代码为:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把Date对象转换成String
*/
public class Demo03DateFormatMethod {
    public static void main(String[] args) {
        Date date = new Date();
        // 创建日期格式化对象,在获取格式化对象时可以指定风格
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = df.format(date);
        System.out.println(str); // 2008年1月23日
    }
}

parse方法

使用parse方法的代码为:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把String转换成Date对象
*/
public class Demo04DateFormatMethod {
    public static void main(String[] args) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = "2018年12月11日";
        Date date = df.parse(str);
        System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
    }
}

练习

请使用日期时间相关的API,计算出一个人已经出生了多少天。

思路:

1.获取当前时间对应的毫秒值

2.获取自己出生日期对应的毫秒值

3.两个时间相减(当前时间– 出生日期)

代码实现:

public static void function() throws Exception {
    System.out.println("请输入出生日期 格式 YYYY-MM-dd");
    // 获取出生日期,键盘输入
    String birthdayString = new Scanner(System.in).next();
    // 将字符串日期,转成Date对象
    // 创建SimpleDateFormat对象,写日期模式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    // 调用方法parse,字符串转成日期对象
    Date birthdayDate = sdf.parse(birthdayString);    
    // 获取今天的日期对象
    Date todayDate = new Date();    
    // 将两个日期转成毫秒值,Date类的方法getTime
    long birthdaySecond = birthdayDate.getTime();
    long todaySecond = todayDate.getTime();
    long secone = todaySecond-birthdaySecond;    
    if (secone < 0){
        System.out.println("还没出生呢");
    } else {
        System.out.println(secone/1000/60/60/24);
    }
}

16.3 Calendar类

概念

java.util.Calendar是日历类,在Date后出现,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。日历类就是方便获取各个时间属性的。

获取方式

Calendar为抽象类,由于语言敏感性,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,返回子类对象,如下:

Calendar静态方法

  • public static Calendar getInstance():使用默认时区和语言环境获得一个日历

例如:

import java.util.Calendar;

public class Demo06CalendarInit {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
    }    
}

常用方法

根据Calendar类的API文档,常用方法有:

  • public int get(int field):返回给定日历字段的值。
  • public void set(int field, int value):将给定的日历字段设置为给定值。
  • public abstract void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  • public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。

Calendar类中提供很多成员常量,代表给定的日历字段:

字段值 含义
YEAR
MONTH 月(从0开始,可以+1使用)
DAY_OF_MONTH 月中的天(几号)
HOUR 时(12小时制)
HOUR_OF_DAY 时(24小时制)
MINUTE
SECOND
DAY_OF_WEEK 周中的天(周几,周日为1,可以-1使用)

get/set方法

get方法用来获取指定字段的值,set方法用来设置指定字段的值,代码使用演示:

import java.util.Calendar;

public class CalendarUtil {
    public static void main(String[] args) {
        // 创建Calendar对象
        Calendar cal = Calendar.getInstance();
        // 设置年 
        int year = cal.get(Calendar.YEAR);
        // 设置月
        int month = cal.get(Calendar.MONTH) + 1;
        // 设置日
        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日");
    }    
}
import java.util.Calendar;

public class Demo07CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2020);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2020年1月17日
    }
}

add方法

add方法可以对指定日历字段的值进行加减操作,如果第二个参数为正数则加上偏移量,如果为负数则减去偏移量。代码如:

import java.util.Calendar;

public class Demo08CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2018年1月17日
        // 使用add方法
        cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天
        cal.add(Calendar.YEAR, -3); // 减3年
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2015年1月18日; 
    }
}

getTime方法

Calendar中的getTime方法并不是获取毫秒时刻,而是拿到对应的Date对象。

import java.util.Calendar;
import java.util.Date;

public class Demo09CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        System.out.println(date); // Tue Jan 16 16:03:09 CST 2018
    }
}

小贴士:

​ 西方星期的开始为周日,中国为周一。

​ 在Calendar类中,月份的表示是以0-11代表1-12月。

​ 日期是有大小关系的,时间靠后,时间越大。

16.4 System类

java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间。
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

currentTimeMillis方法

实际上,currentTimeMillis方法就是 获取当前系统时间与1970年01月01日00:00点之间的毫秒差值

import java.util.Date;

public class SystemDemo {
    public static void main(String[] args) {
           //获取当前时间毫秒值
        System.out.println(System.currentTimeMillis()); // 1516090531144
    }
}

练习

验证for循环打印数字1-9999所需要使用的时间(毫秒)

public class SystemTest1 {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("共耗时毫秒:" + (end - start));
    }
}

arraycopy方法

  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中。

数组的拷贝动作是系统级的,性能很高。System.arraycopy方法具有5个参数,含义分别为:

参数序号 参数名称 参数类型 参数含义
1 src Object 源数组
2 srcPos int 源数组索引起始位置
3 dest Object 目标数组
4 destPos int 目标数组索引起始位置
5 length int 复制元素个数

练习

将src数组中前3个元素,复制到dest数组的前3个位置上复制元素前:src数组元素[1,2,3,4,5],dest数组元素[6,7,8,9,10]复制元素后:src数组元素[1,2,3,4,5],dest数组元素[1,2,3,9,10]

import java.util.Arrays;

public class Demo11SystemArrayCopy {
    public static void main(String[] args) {
        int[] src = new int[]{1,2,3,4,5};
        int[] dest = new int[]{6,7,8,9,10};
        System.arraycopy( src, 0, dest, 0, 3);
        /*代码运行后:两个数组中的元素发生了变化
         src数组元素[1,2,3,4,5]
         dest数组元素[1,2,3,9,10]
        */
    }
}

16.5 StringBuilder类

16.5.1 字符串拼接问题

由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。例如:

public class StringDemo {
    public static void main(String[] args) {
        String s = "Hello";
        s += "World";
        System.out.println(s);
    }
}

在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改。

根据这句话分析我们的代码,其实总共产生了三个字符串,即"Hello""World""HelloWorld"。引用变量s首先指向Hello对象,最终指向拼接出来的新字符串对象,即HelloWord

由此可知,如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。为了解决这一问题,可以使用java.lang.StringBuilder类。

16.5.2 StringBuilder概述

查阅java.lang.StringBuilder的API,StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。

原来StringBuilder是个字符串的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。

它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。原理如下图所示:(默认16字符空间,超过自动扩充)

16.5.3 构造方法

根据StringBuilder的API文档,常用构造方法有2个:

  • public StringBuilder():构造一个空的StringBuilder容器。
  • public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。
public class StringBuilderDemo {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1); // (空白)
        // 使用带参构造
        StringBuilder sb2 = new StringBuilder("itcast");
        System.out.println(sb2); // itcast
    }
}

16.5.4 常用方法

StringBuilder常用的方法有2个:

  • public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。
  • public String toString():将当前StringBuilder对象转换为String对象。

append方法

append方法具有多种重载形式,可以接收任意类型的参数。任何数据作为参数都会将对应的字符串内容添加到StringBuilder中。例如:

public class Demo02StringBuilder {
    public static void main(String[] args) {
        //创建对象
        StringBuilder builder = new StringBuilder();
        //public StringBuilder append(任意类型)
        StringBuilder builder2 = builder.append("hello");
        //对比一下
        System.out.println("builder:"+builder);
        System.out.println("builder2:"+builder2);
        System.out.println(builder == builder2); //true
        // 可以添加 任何类型
        builder.append("hello");
        builder.append("world");
        builder.append(true);
        builder.append(100);
        // 在我们开发中,会遇到调用一个方法后,返回一个对象的情况。然后使用返回的对象继续调用方法。
        // 这种时候,我们就可以把代码现在一起,如append方法一样,代码如下
        //链式编程
        builder.append("hello").append("world").append(true).append(100);
        System.out.println("builder:"+builder);
    }
}

备注:StringBuilder已经覆盖重写了Object当中的toString方法。

toString方法

通过toString方法,StringBuilder对象将会转换为不可变的String对象。如:

public class Demo16StringBuilder {
    public static void main(String[] args) {
        // 链式创建
        StringBuilder sb = new StringBuilder("Hello").append("World").append("Java");
        // 调用方法
        String str = sb.toString();
        System.out.println(str); // HelloWorldJava
    }
}

16.6 包装类

16.6.1 概述

Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型 对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

16.6.2 装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:从基本类型转换为对应的包装类对象。

  • 拆箱:从包装类对象转换为对应的基本类型。

用Integer与 int为例:(看懂代码即可)

基本数值---->包装对象

Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法

包装对象---->基本数值

int num = i.intValue();

16.6.3自动装箱与自动拆箱

由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

16.6.4 基本类型与字符串之间的转换

基本类型转换为String

基本类型转换String总共有三种方式,查看课后资料可以得知,这里只讲最简单的一种方式:

基本类型直接与””相连接即可;如:34+""

String转换成对应的基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

代码使用(仅以Integer类的静态方法parseXxx为例)如:

public class Demo18WrapperParse {
    public static void main(String[] args) {
        int num = Integer.parseInt("100");
    }
}

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

本文章网址:https://www.sjxi.cn/detil/c8e1b87a0c4d4d138a7f41dd6a9aa462

打赏作者

本站为非盈利网站,如果您喜欢这篇文章,欢迎支持我们继续运营!

最新评论
当前未登陆哦
登陆后才可评论哦

湘ICP备2021009447号

×

(穷逼博主)在线接单

QQ: 1164453243

邮箱: abcdsjx@126.com

前端项目代做
前后端分离
Python 爬虫脚本
Java 后台开发
各种脚本编写
服务器搭建
个人博客搭建
Web 应用开发
Chrome 插件编写
Bug 修复