来一位网友的分享
Java是人与计算机之间进行信息交流沟通的一种特殊计算机语言。
Java分为三个不同版本:
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和开发工具
三个步骤:编写源代码,编译源代码,解释执行。
第一步(编写源代码):新建 新建文本文档.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
目录的完整路径:
win +r输入cmd,输入:cd +空格 + 粘贴的目录:
编译命令:javac HelloWorld.java
执行后可以看到生成了一个HelloWorld.class跨平台文件
第三步(解释执行):执行HelloWorld.class,使用命令:java HelloWorld:(注意不带、不带、不带
文件后缀.class)
IDE是集成开发环境:Integrated Development Environment的缩写。使用IDE的好处在于,可以把编写代码、组织项目、编译、运行、调试等放到一个环境中运行,能极大地提高开发效率。
IntelliJ Idea是由JetBrains公司开发的一个功能强大的IDE,分为免费版和商用付费版。JetBrains公司的IDE平台也是基于IDE平台+语言插件的模式,支持java开发环境、Python开发环境、Ruby开发环境、PHP开发环境等,这些开发环境也分为免费版和付费版。
<img src="Java精讲精练.assets/2021-02-25_212832.png" alt="2021-02-25_212832" />
所谓包,就是文件夹,用来放置源文件(.java文件)。
程序解压后的目录 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
打开软件。
第一次运行提示如下:
<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" />
第一步:点击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
中打开。
第四步:选择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" />
完成如下:
第七步:在src右键,新建Java class
文件
第八步:输入:类名称 HelloWorld
前八步总结:
第九步:编写源代码并运行
m
,待main方法提示出来后按回车
或Tab
键,自动生main方法;sout
自动生成打印输出语句;Hello, World!
'HelloWorld.main()'
// 这是单行注释。
===============================================================================
/*
这是多行注释;
这是多行注释。
*/
===============================================================================
//关键字是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); 报错
}
}
四大类,八种基本数据类型:
byte:字节型,-128~127
short:短整型,-32768~32767
int:整型,约正负21亿
long:长整型
float:单精度浮点型
double:双精度浮点型
char:字符型
boolean:布尔型
Java中的默认类型:整数类型是 int
、浮点类型是 double
。long
类型:数据后加L
表示。 float
类型:数据后加F
表示。
//变量分两步来定义
数据类型 变量名;//创建一个变量
变量名 = 数据值;//将右边的值,赋值给左边的变量
//或者一步到位定义
数据类型 变量名 = 数据值;
========================================================
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
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
}
}
//作用域理解,从定义变量的一行开始,一直到直接所属的大括号结束为止。
{
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
//如上,可以这么写,但不推荐
把一个数据范围小
的数值
或者变量
赋值给另一个数据范围大
的变量
,会出现数据类型的自动转换现象。这种转换方式是自动的。 不需要你写代码实现。
示例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类型不能与其他基本数据类型相互转换。
强制类型转换是将 取值范围大的类型
强制转换成 取值范围小
的类型 。
强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出。
需要你写代码实现。
范围小的类型 范围小的变量名 = (范围小的类型)范围大的数据;
====================================================
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,大杯往小杯倒水,数据丢失
}
}
示例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
赋值运算符,就是将符号右边的值,赋给左边的变量。
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可能会有损失。
}
}
比较运算符的结果都是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
}
}
逻辑运算符是用来连接两个布尔类型结果的运算符,运算结果都是布尔值 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
}
}
逻辑与&,无论左边真假,右边都要执行。
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
逻辑或|,无论左边真假,右边都要执行。
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。
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
}
}
数据类型 变量名称 = 条件判断 ? 表达式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
}
}
实际编写代码时,尽量使用括号()
实现想要的运算顺序,以免产生歧义。
public static void main(String[] args){
//顺序执行,根据编写的顺序,从上到下运行
System.out.println(1);
System.out.println(2);
System.out.println(3);
}
//单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无关。
//标准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
}
//复合、扩展的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("星期日");
}
}
}
//定义变量,取a和b的较大值
int a = 10;
int b = 20;
int c;
if (a > b) {
c = a;
} else {
c = b;
}
//上述功能可以改写为三元运算符形式
c = a > b ? a : b;
if格式1,模板名称ifm
;布尔表达式为一般为比较运算。
//初始化语句
if ($END$) { //布尔表达式
}
if格式2,模板名称ifelse
//初始化语句
if ($END$) { //布尔表达式
} else {
}
if格式3,模板名称可设置为eif
//初始化语句
if ($END$) { //布尔表达式
} else if () { //布尔表达式
} else if () { // Ctrl+D复制多个
} else {
}
//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都无所谓了,因为代码都要结束了。
}
}
}
自定义代码块,模板名称定义为sw
//初始化语句
switch ($END$) { //表达式,只能返回byte,short,char,int,enum枚举,String字符串 类型的值。
case : //常量
break;
case :
break;
default:
break;
}
/*
循环基本组成:
初始化表达式:在循环最初执行,而且只做一次。
布尔表达式(条件判断):如果成立,则循环继续;如果不成立,则循环退出。
循环体:重复要做的事情,若干行语句。
步进:每次循环之后都要进行的扫尾工作。
*/
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);
}
**执行流程 **
输入fori
按Tab
初始化表达式①;
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);
}
**执行流程 **
自定义代码,模板名称定义为wh
//初始化语句
while ($END$){ //布尔表达式
//循环体
//步进表达式
}
初始化表达式①
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);
**执行流程 **
自定义代码,模板名称定义为dw
//初始化语句
do {
//循环体
//步进表达式
} while ($END$); //布尔表达式
循环中的条件永远为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); //布尔表达式
注意:在死循环下面放代码会提示错误: 无法访问的语句。
**使用场景:终止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 + "次");
}
}
}
**使用场景:结束本次循环,继续下一次的循环 **
示例:不加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 + "次");
}
}
}
嵌套循环,是指一个循环的循环体是另一个循环。比如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分
--------
嵌套循环执行流程:
理解:
结论:
容器:是将多个数据存储到一起,每个数据称为该容器的元素。
生活中的容器:水杯,食堂,教室。
数组就是存储数据长度
固定的容器,且多个数据的数据类型是一致的。 数据类型含八大类和引用数据类型。数组长度一旦指定,不可更改。
数组也是一种引用数据类型。
静态初始化,给数组指定内容。
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
================================================================
int[] arrayA = new int[]{1,2,3,4,5};
//可以拆分成两个步骤:
int[] arrayA;
arrayA = new int[]{1,2,3,4,5};
数据类型[] 数组名 = {元素1,元素2,元素3...};
========================================================
int[] arrayA = {1,2,3,4,5};//不可拆分
动态初始化指定长度。
数组存储的数据类型[] 数组名字 = new 数组存储的数据类型[长度];
============================================================
int[] arrayB = new int[5];
//可以拆分成两个步骤:
int[] arrayB;
arrayB = new int[5];
如果不确定数组内容用动态初始化,确定内容用静态初始化。
静态初始化,arj
$VAR1$[] $VAR2$ = {$VAR3$};$END$
静态初始化,arj2
$VAR1$[] $VAR2$ = new $VAR3${$VAR4$};$END$
动态初始化,ard
$VAR1$[] $VAR2$ = new $VAR3$;$END$
每一个存储到数组的元素,都自动拥有一个编号,从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);
}
}
如果是整数类型,那么默认为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
}
}
内存是计算机中的重要原件,临时存储区域,作用是运行程序。
编写的程序存放在硬盘中,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。 Java虚拟机要运行程序,必须要向操作系统申请“一块”内存,并对内存进行空间的分配和管理。
Java把申请过来的内存划分为5个部分:
栈(Stack,线性表):存放的都是方法中的局部变量。方法的运行一定要在栈当中。
堆(Heap):凡是new出来的东西,都在堆当中。所以数组是在堆中存放。
方法区(Method Area):存储.calss相关信息,包含方法的信息(含main、自定义方法)。注意是.class内的都存储,但只是存了方法的死信息,方法的运行一定要在栈当中。这个动作叫做进栈。
本地方法栈(Native Method Stack):与操作系统相关,与开发无关。
寄存器(PC Register):与CPU相关,与开发无关。
互不相关模型
把一个数组的地址值赋值给另一个数组,叫做引用。其实就是操作同一个数组。
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
引用类型(含数组)可以赋一个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]);
数组一旦创建,在程序运行期间,长度不可改变。
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
}
}
如上,既然数组一旦创建长度不可变,但是为什么上面代码的数组长度会不一样。
将数组中的每个元素分别获取出来,就是数组遍历。
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]);
}
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);
}
//求最小值
数组元素反转,就是对称位置的元素交换。比如:1和5交换,2和4交换。需要两个编号进行交换。
如下图,交换原理分析(假如条件判断为: <=
,可以吗?):
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]);
}
}
常用冒泡排序法:不断比较数组中相邻的两个元素,较小者向上浮,较大都向下沉,与汽泡上浮相似。
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]);
}
}
}
//练习:降序如何写?
将一个特定功能的代码定义在一个大括号内,就是一个方法;当需要这个功能时,就去调用方法。
注意:
修饰符 返回值类型 方法名 (参数列表){
代码...
return 返回值;//void 可以不写
}
===========================================================
//public是公共的方法,可以被任何一个类调用。
//static是静态修饰,说明这个函数是属于类的,在调用时不需要再创建对象。
//void是返回值类型,表示没有返回值。返回值是从方法中出来的数据,因此void可以不写return。
//方法名称()用小驼峰命名,它括号内的叫做参数列表,参数是进入方法的数据,目前是无参数格式。
public static void 方法名称() {
方法体
}
============================================================
//private是私用的方法,也就是只能在本类中被调用,任何其他类都不能调用。
private static void 方法名称() {
方法体
}
=============================================================
//方法格式1
public static void methodName() {
System.out.println("这是一个方法");
}
本类调用
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();
}
}
正确写法
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(){
}
}
}
修饰符 返回值类型 方法名 (参数列表){
代码...
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写法,只能单独调用,不能进行打印调用和赋值调用。
示例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行冲突。
数组作为方法参数传递,传递的参数是数组内存的地址。
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]);
}
}
数组作为方法的返回值,返回的是数组的内存地址。
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;
}
即:方法的参数为基本类型时,传递的是数据值;方法的参数为引用类型时,传递的是地址值。
Java语言是一种面向对象的程序设计语言。
这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。
面向对象思想就是参照现实中的事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。
它区别于面向过程思想(C语言),强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
举例
区别
**特点 **
面向对象更符合人的思考习惯,将复杂的事情简单化,让我们从执行者变成了指挥者。
看个实际代码理解
面向过程(打工人):当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节。 面向对象(老板):当需要实现一个功能的时候,不关心具体的步骤,而是找一个具备该功能的人,来帮我做事。
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));
}
}
如上可见,收集轮子是很重要的一件事,轮子一般自己写,或者"看"别人写的,开发就是重复搬砖工作。
类是一组相关属性和行为的集合,可以看成是一类事物的模板。使用属性特征和行为特征来描述该类事物。
属性:就是该事物的状态信息。
行为:就是该事物能够做什么。
比如:
对象是一类事物的具体体现,对象是类的一个实例。具备该类事物的属性和行为。
比如:
**类与对象的关系 **
类是对一类事物的描述,是抽象的。如,猫科动物
对象是一类事物的实例,是具体的。如,黑猫、白猫能抓老鼠的都是好猫
类是对象的模板,对象是类的实体。
实例化一个对象
<img src="Java精讲精练.assets/image-20210426194808559.png" alt="image-20210426194808559" style="zoom:50%;" />
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("喵喵叫");
}
}
通常情况下,一个类并不能直接使用,需要根据类创建一个对象才能使用。
针对类、成员方法、成员变量,有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();
}
}
在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。 如上,main方法最后出栈。
对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间
猫的类如下:
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);//黄色
}
}
现在以手机为例,分析程序运行流程:
变量根据定义位置的不同,我们给变量起了不同的名字。
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);//可以使用
}
}
在类中的位置不同
作用范围不一样
初始化值的不同
在内存中的位置不同
生命周期不同
封装性在Java中的体现:
private
也是一种封装封装将一些代码隐藏起来,外界无法直接操作和修改,要访问它,必须通过指定的方式。
封装是一个保护屏障,防止封装的代码被随意访问,加强了代码的安全性。
适当的封装可以让代码更容易理解与维护,因为你不用关注他用什么代码实现。
封装原则
将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
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);
先看案例:
//定义一个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 修饰,可以省略不写 。
当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
当我们通过关键字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岁
总结
JavaBean
是 Java语言编写类的一种标准规范。
一个标准的类通常要有以下四个部分:
还是以之前的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;
}
}
如上,我们其实只关注name
和age
,但却写了一堆代码。我只想写name
和age
的两行代码,其它都不想动手。
第一步:输入:
public class Person {
private String name;
private int age;
}
第二步:生成无参数构造方法:把光标放在到第4行,Code-->>generate(生成)【或者按快捷键:alt+insert】
第三步:生成全参数构造方法(按住shift或ctrl可多选参数)
第四步:生成set、get方法
API(Application Programming Interface),应用程序编程接口。
Java API是一本程序员的 字典 ,是JDK中提供给我们使用的类的说明文档。
这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。
所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们
JDK_API_1_6_zh_CN.CHM官方(sun)翻译版本,后续的官方不再翻译。
基本使用:
双击打开CHM帮助文档,点击显示
,找到索引,看到输入框。在输入框里输入Scanner
,然后两次enter确认。
一个可以解析基本类型和字符串的简单文本扫描器。可以实现把键盘输入的数据传递到程序当中。
使用步骤
1、导包。
首先根据上一节的方法,查询API文档,发现Scanner 类在java.util包下。(java.lang包下的所有类无需导入)
示例:
//导包的语句需要定义在类的上面。
//格式:import 包名.类名;
import java.util.Scanner;
2、创建对象(在构造方法摘要查看)
使用该类的构造方法,创建一个该类的对象。
<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);
}
}
练习:求和、取最值
没有名称的对象就叫匿名对象。
基于一个类来讲解:
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();
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);
}
}
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);
}
}
使用步骤
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;随机生成双色球彩票。
首先学习什么是对象数组?
示例:定义一个数组,用来存储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());//小明
}
}
以上是对象数组基本用法,但是数组有一个缺点:一旦创建,程序运行期间长度不可以发生改变。
一般程序开发时,定不下来要几个元素,还在考虑变化中,如果你创建小了,后面又想加大数组,会非常麻烦!
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);错误
}
}
对于元素的操作,基本体现在——增、删、查。常用的方法有:
//常用方法
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));
}
}
}
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]
}
}
练习:
程序当中所有的双引号字符串,都是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底层是靠字符数组实现的。
常见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 它是通过默认字符集解码的,中文涉及到编码等后续再说
}
}
String str = "abc";
程序当中直接写上的双引号字符串,就在字符串常量池中。
对于基本类型来说,==
是进行数值的比较。
对于引用类型来说,==
是进行地址值的比较。
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
}
}
==
是对象的地址值比较,如果确实需要字符串的内容比较,可以使用两个方法。
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
}
}
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的改变其实只是地址值的改变
}
}
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);//我***
}
}
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参数是一个正则表达式,如果按照英文句点分割,必须写\\.
练习:键盘输入一个字符串,统计其中各种字符出现的次数;种类有:大写字母、小写字母、数字、其他。
先看一个例子:所在教室
每个对象都一样,共享才省内存空间。
加入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教室
一旦使用static修饰成员方法,就变成了静态方法。静态方法不属于对象,而是属于类。
如果成员方法没有static
关键字,那么必须先创建对象,然后才能通过对象调用它。
注意:
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,都推荐使用类名称进行调用。
静态变量:类名称.静态变量
静态方法:类名称.静态方法()
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后执行结果:(静态不会再执行了!)
静态代码块执行了!
构造方法执行了!
构造方法执行了!
*/
}
}
静态代码块的典型用途:用来一次性地对静态成员变量进行赋值。
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]
}
}
练习
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
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接 访问父类中的非私有的属性和行为。
如上图:
定义父类的格式:(一个普通的类定义)
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();//父类方法执行了!
}
}
成员变量不重名时:
//父类
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.等号左边是谁,就优先用谁
//父类
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
成员方法不重名
//父类
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();//子类方法执行了!
}
}
注意事项:无论是成员方法还是成员变量,如果没有都是向上找,绝对不会向下找子类的。
重写概念:在继承关系当中,方法的名称一样,参数列表也一样。
重写(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%,我们写父类和子类方法的返回值、权限都是相等关系。
以一个手机生产商为例,假设它的生产线、技术是可以继承沿用的,即生产新手机可以利用旧手机的流水线。
已经投入使用的类,在生产环境中有很多类(代码)在使用它,改动它会导致整个系统崩溃,但通过继承的覆盖来添加改动就可以,这就是继承存在的最大意义。
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();
}
}
先看一个案例:
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()给你。
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.在子类的构造方法中,访问父类的构造方法。(前面已讲)
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){
}
}
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //正确
class C extends A,B...{} //错误,两个父类导致子类不懂听谁的,即成员变量、方法不懂用谁的好。
class A{}
class B extends A{}
class C extends B{}
顶层父类是Object类。所有的类默认继承Object,作为父类。
练习:
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。
我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类
如下图:图形父类中计算面积的方法,因形状太多写不了,只能是抽象的方法类中计算面积的方法,因形状太多写不了,只能是抽象的方法。
动物吃的方法也不够具体,也只能是抽象的方法。
抽象方法定义
修饰符 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() {
}
}
//抽象类
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()
}
}
练习:
接口就是一种公共的规范标准,谁都可以用,提高了便利。
在Java当中,接口就是多个类的公共规范,是一种引用类型。
接口是方法的集合,它的内部主要就是封装了方法 ,包含:
常量
抽象方法(JDK 7及以前 )
默认方法(JDK8)
静态方法(JDK8)
私有方法(JDK9)
//说明:interface表示接口,它编译后的字节码仍然是.class
public interface 接口名称 {
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}
如果是JDK7,那么接口中可以包含的内容有:
如果是JDK8,那么接口中可以包含的内容有:
如果是JDK9,那么接口中可以包含的内容有:
具体操作
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();
}
实现类的格式:
class 类名 implements 接口名 {
//重写接口中的所有抽象方法【必须】
}
//示例:
//实现类的命名建议,在接口上加Impl,增强可读性
public class MyInterfaceImpl implements MyInterface{
}
一个完整的示例:
先写:
public class MyInterfaceImpl implements MyInterface{
}
在{}中:alt+insert 生成
<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();
}
}
格式:
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重写了接口的默认方法
}
}
格式:
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();
}
}
如果接口中有两个方法的内容重复内容太多了,我们就抽取共性。如下:
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开始,接口当中允许定义私有方法。
private 返回类型 方法名称(参数列表){
方法体;
}
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();//错误
}
}
接口可以定义“成员变量”,使用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,同时有常量、抽象方法、默认方法、静态方法、私有方法,并定义一个实现类、使用类。
描述的是对象的多种形态。
<img src="Java精讲精练.assets/image-20210601000735526.png" alt="image-20210601000735526" style="zoom: 67%;" />
对象多态一句话描述:父类引用指向子类对象。左父右子。
父类名称 父类对象名 = 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();//父类特有方法
}
}
直接通过对象名称访问成员变量。
//父类
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
}
}
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。
//父类
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();父类没有这个方法,编译器找不到,错误
}
}
多态中成员变量只能使用父类自己的,成员方法是反过来,优先使用子类的,没有才用自己的,但首要条件也要自己有方法才行。
多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。
如图,Teacher讲师、Assistant助教、Employee员工。
在老板眼里,不管你是什么岗位,他只关心你工作不工作,你工作了才发钱。因此你使用多态时,左侧统一都是员工,右侧就根据工作需要new谁,叫谁去干活。
用了多态,代码更直观,父子一眼看出,便于归类,编写简便,扩展性好。
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();//猫吃鱼
}
}
缺点:子类独有的方法,也即父类没有的方法,不能调用。
//动物类
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; 编译不会报错,但运行会,提示类型转换异常!
}
}
//得到一个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();
}
}
}
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;
}
}
什么是内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
例如:身体和心脏的关系、汽车和发动机的关系,两者都是缺一不可。
分类:
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);//内部类可以使用外部类的东西。
}
}
}
两种方式:
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();//内部类方法
}
}
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();
}
}
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
局部理解:只有在当前方法当中的才能使用,出到方法外面就不能使用。
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
}
}
/*
为什么用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
}
}
}
}
回忆一个正常的接口用法
//接口
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没必要写了,这是匿名的好处。
}
}
实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步 去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类 型也是可以的。
首先理解:不管什么系统,升级更新是很常见的、必须的,因此写的代码必须具体良好的扩展性。
初步定义一个类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类里?
接口是对方法的封装,对应游戏当中,可以看作是扩展游戏角色的技能。所以,如果想扩展更强大技能,我们在 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();
结果:
盖伦发动法术攻击:纵横天下
攻击完毕
盖伦发动法术攻击:逆转乾坤
攻击完毕
我们使用一个接口,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程序的扩展性。
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)
:指示其他某个对象是否与此对象“相等”。方法摘要
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()方法。
方法摘要
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等其他方法,今后学习。
在刚才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));
}
概述
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()
把日期对象转换成对应的时间毫秒值。java.text.DateFormat
是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。
构造方法
由于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);
}
}
概念
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月。
日期是有大小关系的,时间靠后,时间越大。
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]
*/
}
}
由于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
类。
查阅java.lang.StringBuilder
的API,StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。
原来StringBuilder是个字符串的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。
它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。原理如下图所示:(默认16字符空间,超过自动扩充)
根据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
}
}
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
}
}
Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:
装箱:从基本类型转换为对应的包装类对象。
拆箱:从包装类对象转换为对应的基本类型。
用Integer与 int为例:(看懂代码即可)
基本数值---->包装对象
Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法
包装对象---->基本数值
int num = i.intValue();
由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
基本类型转换为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
异常。
本站为非盈利网站,如果您喜欢这篇文章,欢迎支持我们继续运营!
本站主要用于日常笔记的记录和生活日志。本站不保证所有内容信息可靠!(大多数文章属于搬运!)如有版权问题,请联系我立即删除:“abcdsjx@126.com”。
QQ: 1164453243
邮箱: abcdsjx@126.com