文章目录
第1章 java概述
1、java 百度百科
- Java 是一个通用术语,用于表示 Java 软件及其组件,包括“Java 运行时环境 (JRE)”、“Java 虚拟机 (JVM)”以及“插件”。
- Java具有大部分编程语言所共有的一些特征,被特意设计用于互联网的分布式环境。Java具有类似于C++语言的形式和感觉,但它要比C++语言更易于使用,而且在编程时彻底采用了一种以对象为导向的方式。
2、JDK,JRE 基本介绍
(1) JDK 的全称(Java Development Kit Java开发工具包)
JDK=JRE + java 的开发工具【java, javac,javadoc,javap 等】
(2) JDK 是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
(2) JRE (Java Runtime Environment Java运行环境)
JRE=JVM + Java的核心类库[类]
包括.Java虚拟机(JVMJava Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
(3) JDK、JRE和JVM的包含关系
JDK=JRE+开发工具集(例如Javac,java编译工具等)
JRE=JVM+Java SE标准类库(java核心类库)
如果只想运行开发好的.class文件只需要.JRE
3、 java下载安装配置
官网下载JDK:
https://www.oracle.com/java/technologies/downloads/archive/#JavaSE
配置环境变量:
目的:在任意磁盘目录下执行java命令
(1)我的电脑--属性--高级系统设置--环境变量
(2)增加JAVA HOME 环境变量,指向jdk的安装目录C:\Program Files (x86)\Java\jdk1.8.0_361
(3)编辑path 环境变量,增加%JAVA HOME%\bin和%JAVA_HOME%\jre\bin
(4)打开DOS命令行,任意目录下敲入javac/java。如果出现javac的参数信息,配置成功。
注意:配置用户环境变量只对本用户生效,配置系统环境变量对所有用户生效
4、Java开发预热
4.1 Java开发注意事项和细节说明
(1) Java源文件以.java为扩展名。源文件的基本组成部分是类(class)。
(2) Java应用程序的执行入口是main()方法。它有固定的书写格式:public static void main(Stringl] args){...}
(3) Java语言严格区分大小写。
(4) 一个源文件中最多只能有一个public类。其它类的个数不限。
(5) 如果源文件包含一个public类,则文件名必须按该类名命名!
(6) 一个源文件中最多只能有一个public类。其它类的个数不限,也可以将main方法写在非public类中,
然后指定运行非public类,这样入口方法就是非public的main方法
4.2 注释类型
1) 单行注释 //
2) 多行注释 /* */
3) 文档注释 /** *
4.3 文档注释
由JDK的javadoc所解析,生成一套网页文件形式体现该程序的说明文档,一般写在类。
//执行命令
javadoc -d 文件夹路径名下 -xx -yy Demo.java
//例如:javadoc -d D:\temp -author -version Comment.java
4.4 Java API接口文档
API (Application Programming Interface,应
用程序编程接口)是Java提供的基本编程接口(java提供的类还有相关的方法)。中文在线文档:https://www.matools.com/api/java8
4.5 DOS命令简介
Dos: Disk Operating System 磁盘操作系统。
1)查看当前目录是有什么内容 dir
dir dir E:\kugou\temp
2)切换到其他盘下 盘符号 cd(change directory)
cd /D c:
3)切换到当前盘的其他目录下(使用相对路径和绝对路径演示), ..\表示上一级目录
E:\>cd E:\Kugou E:\>cd ..\..\Kugouu
4)切换到上一级
cd ..
5)切换到根目录
cd \
6)查看指定的目录下所有的子级目录
tree
7) 清屏
cls
8)退出DOS
exit
9)其他指令
md 创建目录 rd 删除目录 copy 拷贝文件 del 删除文件 .......
第2章 变量、数据类型、运算符
1、变量
变量是程序的基本组成单位,变量有三个基本要素(类型+名称+值)
变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房
间,而通过变量名可以访问到变量(值)
1.1、使用
1)声明变量 int a;
2)赋值 a= 60;
3)直接声明赋值 String str = "code SE";
1.2 注意细节
1.变量表示内存中的一个存储区域[不同的变量,类型不同,占用的空间大小不同,比如: int 4个字节,double 就是8个字节
2、该区域有自己的名称[变量名]和类型[数据类型]
3.变量必须先声明,后使用,即有顺序
4.该区域的数据/值可以在同一类型范围内不断变化
5.变量在同一个作用域内不能重名
6.变量=变量名+值+数据类型,变量三要素
1.3 java程序中 + 的使用
1.当左右两边都是数值型时,则做加法运算
2.当左右两边有一方为字符串,则做拼接运算
3.运算顺序,是从左到右
System.out.printIn(100+ 98);//198
System.out.println("100"+ 98)://10098
System.out.println(100+3+"hello");//103hello
System.out.println("hello" +100+3); //hello1003
2、数据类型
2.1 整数类型
Java的整数类型就是用于存放整数值的,比如1,2 ,50,345678等等
byte n1 = 10; //1个字节
short n2 = 10; //2个字节
int n3 = 10; //4 个字节
long n4 = 10; //8 个字
整型的注意细节:
1. Java各整数类型有固定的范围和字段长度,不受具体OS[操作系统]的影响,以保证java程序的可移植性。
2. Java的整型常量(具体值)默认为int型,声明long型常量须后加I或L
3. java程序中变量常声明为int型,除非不足以表示大数,才使用long
4. bit:计算机中的最小存储单位。byte:计算机中基本存储单元,1byte = 8 bit.
2.3 浮点类型
Java 的浮点类型可以表示一个小数,比如 123.4 ,7.8 ,0.12
说明:(1) 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位 (2) 尾数部分可能丢失,造成精度损失(小数都是近似值)
浮点类型注意细节:
1.与整数类型类似,Java浮点类型也有固定的范围和字段长度,不受具体OS的影响。[float 4个字节double是8个字节]
2. Java的浮点型常量(具体值)默认为double型,声明float型常量,须后加‘f或‘F'
3.浮点型常量有两种表示形式:
1)十进制数形式:如:5.12 512.0f.512(必须有小数点)
2)科学计数法形式:如:5.12e2 [5.12*10的2次方]5.12E-2 [5.12/10的2次方]
4.通常情况下,应该使用double型,因为它比float型更精确。
public class FloatDetail {
public static void main(String[] args) {
//Java 的浮点型常量(具体值)默认为 double 型,声明 float 型常量,须后加‘f’或‘F'
float num2 = 1.1F; //对的
double num3 = 1.1; //对
double num4 = 1.1f; //对
//十进制数形式:如:5.12 512.0f .512 (必须有小数点)
double num5 = .123; //0.123
System.out.println(num5);
//科学计数法形式:如:5.12e2 [5.12 * 10 的 2 次方 ] 5.12E-2 []
System.out.println(5.12e2);//512.0
System.out.println(5.12E-2);//0.0512
//通常情况下,应该使用 double 型,因为它比 float 型更精确。
//[举例说明]double num9 = 2.1234567851;float num10 = 2.1234567851F;
double num9 = 2.1234567851;
float num10 = 2.1234567851F;
System.out.println(num9);
System.out.println(num10);
//浮点数使用陷阱: 2.7 和 8.1 / 3 比较
//看看一段代码
double num11 = 2.7;
double num12 = 2.7; //8.1 / 3; //2.7
System.out.println(num11);//2.7
System.out.println(num12);//接近 2.7 的一个小数,而不是 2.7
//得到一个重要的使用点: 当我们对运算结果是小数的进行相等判断是,要小心
//应该是以两个数的差值的绝对值,在某个精度范围类判断
if (num11 == num12) {
System.out.println("num11 == num12 相等");
}
//正确的写法
if (Math.abs(num11 - num12) < 0.000001) {
System.out.println("差值非常小,到我的规定精度,认为相等...");
}
}
}
2.4 字符类型(char)
字符类型可以表示单个字符,字符类型是char,char 是两个字节(可以存放汉字),多个字符我们用字符串 String。
char c1 = 'a';
char c2 = '好';
char c3 ='\t';
char c4 = 97; //a
细节:
1.字符常量是用单引号(‘’)括起来的单个字符。例如:char c1 = 'a';char c2 ='中"; char c3 = '9";
2. Java中还允许使用转义字符′来将其后的字符转变为特殊字符型常量。例如:char c3 =‘\n'; '\n'表示换行符
3.在java中,char的本质是一个整数,在输出时,是unicode码对应的字符。http://tool.chinaz.com/Tools/Unicode.aspx
4.可以直接给char赋一个整数,然后输出时,会按照对应的unicode字符输出[97-》a]
5. char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码.
public static void main(String[] args) {
//在 java 中,char 的本质是一个整数,在默认输出时,是 unicode 码对应的字符
//要输出对应的数字,可以(int)字符
char c1 = 97;
char c2 = 'b'; //输出'b'对应的数
char c3 = '中';
char c4 = 20013;
System.out.println(c1); // a
System.out.println((int) c2); //20013
System.out.println((int) c3); //98
System.out.println(c4); //中
//char 类型是可以进行运算的,相当于一个整数,因为它都对应有 Unicode 码.
System.out.println('a' + 10);//107
}
ASSCII编码、Unicode编码、UFT-8编码介绍:
2.5 布尔类型(boolean)
布尔类型也叫boolean类型,booolean类型数据只允许取值true和false,无null。
boolean类型占1个字节。
boolean类型适于逻辑运算,一般用于程序流程控制。
3、基本数据类型转换
3.1 自动类型转换
3.2 强制转换
是自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意。
3.3 基本数据类型和 String 类型的转换
在程序开发中,经常需要将基本数据类型转成String类型。或者将String类型转成基本数据类型。
- 基本类型转String类型 , 语法:将基本类型的值+""即可
- String类型转基本数据类型,语法:通过基本类型的包装类调用parseXX方法即可
上代码:
public class ToString1 {
public static void main(String[] args) {
int n1 = 100;
float n2 = 3.3f;
double n3 = 3.1415926;
boolean b1 = false;
//基本数据类型->String
String str1 = n1 + "";
String str2 = n2 + "";
String str3 = n3 + "";
String str4 = b1 + "";
System.out.println("str1=" + str1 + "\t" + "str2=" + str2 + "\t" + "str3=" + str3 + "\t" + "str4=" + str4);
//str1=100 str2=3.3 str3=3.1415926 str4=false
//String->对应的基本数据类型
//使用 基本数据类型对应的包装类的相应方法,得到基本数据类型
String s5 = "123";
int num1 = Integer.parseInt(s5);
double num2 = Double.parseDouble(s5);
float num3 = Float.parseFloat(s5);
long num4 = Long.parseLong(s5);
byte num5 = Byte.parseByte(s5);
boolean b = Boolean.parseBoolean("true");
short num6 = Short.parseShort(s5);
System.out.println("num1=" + num1 + "\t" + "num2=" + num2 + "\t" + "num3=" + num3 + "\t" + "num4=" + num4 + "\t" + "num5=" + num5 + "\t" + "b=" + b + "\t" + "num46=" + num6);
//num1=123 num2=123.0 num3=123.0 num4=123 num5=123 b=true num46=123
//得到 s5 字符串的第一个字符 '1'==> s5.charAt(0);
System.out.println(s5.charAt(0));//1
}
}
注意:
1)在将String类型转成基本数据类型时,要确保String类型能够转成有效的数据,比如我们可以把“123",个整数,但是不能把"hello"转成一个整数
2)如果格式不正确,就会抛出异常,程序就会终止
4、运算符
4.1 算术运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。
(1) 算术运算符 (2) 赋值运算符 (3) 关系运算符 [比较运算符]
(4) 逻辑运算符 (5) 位运算符 [需要二进制基础] (6) 三元运算符
运算符:
4.2关系运算符(比较运算符)
4.2.1介绍
1)关系运算符的结果都是boolean型,也就是要么是true,要么是false
2)关系表达式经常用在if结构的条件中或循环结构的条件中
4.3逻辑运算符
4.3.1 介绍
用于连接多个条件(多个关系表达式),最终的结果也是一个boolean值
4.3.2逻辑运算符一览
分为两组
1)短路与&& ,短路或,取反!
2)逻辑 与 &,逻辑 或 |,逻辑 异或
4.3.3 说明逻辑运算规则:
1) a&b : & 叫逻辑与:规则:当 a 和 b 同时为 true ,则结果为 true, 否则为 false
2) a&&b : && 叫短路与:规则:当 a 和 b 同时为 true ,则结果为 true,否则为 false
3) a|b : | 叫逻辑或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false
4) a||b : || 叫短路或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false
5) !a : 叫取反,或者非运算。当 a 为 true, 则结果为 false, 当 a 为 false 是,结果为 true
6) a^b: 叫逻辑异或,当 a 和 b 不同时,则结果为 true, 否则为 false
4.3.4 &&
和&
基本规则
名称 | 语法 | 特点 |
---|---|---|
短路与&& | 条件1&&条件2 | 两个条件都为true,结果为true,否则 false |
逻辑与& | 条件1&条件2 | 两个条件都为true,结果为true,否则false |
public class LogicOperator1 {
public static void main(String[] args) {
//&&短路与和&
int age = 50;
if (age > 30 && age < 100) {
System.out.println("OK");
}
//&逻辑与使用
if (age > 20 & age < 90) {
System.out.println("ok200");
}
//区别
int a = 10;
int b = 20;
//对于&&短路与而言,如果第一个条件为 false ,后面的条件不再判断
// 对于&逻辑与而言,如果第一个条件为 false ,后面的条件仍然会判断
if (a < 1 & ++b < 50) {
System.out.println("ok300");
}
System.out.println("a=" + a + " b=" + b);// 10 21
}
}
注意&&和&使用区别
1) &&短路与:如果第一个条件为false,则第二个条件不会判断,最终结果为false,效率高
2)&逻辑与:不管第一个条件是否为 false,第二个条件都要判断,效率低
3)开发中,使用的基本是使用短路与&&,效率高
4.3.5 ||
和|
基本规则
名称 | 语法 | 特点 |
---|---|---|
短路或|| | 条件1||条件2 | 两个条件中只要有一个成立,结果为true,否则为false |
逻辑或| | 条件1|条件2 | 只要有一个条件成立,结果为true,否则为false |
public class LogicOperator2 {
public static void main(String[] args) {
// || 规则: 两个条件中只要有一个成立,结果为 true,否则为 false
// | 规则: 两个条件中只要有一个成立,结果为 true,否则为 false
int age = 50;
if (age > 20 || age < 30) {
System.out.println("ok100");
}
if (age > 20 | age < 30) {
System.out.println("ok200");
}
//看看区别
// (1)||短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高
// (2)| 逻辑或:不管第一个条件是否为 true,第二个条件都要判断,效率低
int a = 4;
int b = 9;
if (a > 1 || ++b > 4) { // 可以换成 | 测试
System.out.println("ok300");
System.out.println("a=" + a + " b=" + b);//4 9
}
}
}
注意||和│使用区别
1)||短路或:如果第一个条件为true,则第二个条件不会判断,最终结果为true,效率高
2)│逻辑或:不管第一个条件是否为true,第二个条件都要判断,效率低
3)开发中,我们基本使用Ⅱ
4.3.6 !
取反
名称 | 语法 | 特点 |
---|---|---|
! 非(取反) | !条件 | 如果条件本身成立,结果为 false,否则为 true |
4.3.7 ^
逻辑异或
a^b:叫逻辑异或,当a和 b 不同时,则结果为true,否则为false
System.out.println((2<4)^(9>10));//true
4.4 赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量。
4.4.21 赋值运算符的分类
基本赋值运算符 | = | a = 10; |
复合赋值运算符 | += ,-= ,*= , /= ,%= | a += b;【等价a = a+ b; 】 a -=b;【等价a=a - b; 】 |
4.4.2 赋值运算符特点
1)运算顺序从右往左int num= a+b+c;
2)赋值运算符的左边只能是变量,右边可以是变量、表达式、常量值int num= 20; int num2=78*34 -10; int num3=a;
3)复合赋值运算符等价于下面的效果比如: a+=3;等价于a=a+3;其他类推
4)复合赋值运算符会进行类型转换。byte b= 2; b+=3; b++;
4.5 三元运算符
基本语法
条件表达式?表达式1:表达式2;
运算规则:
1.如果条件表达式为true,运算后的结果是表达式1;
2如果条件表达式为false,运算后的结果是表达式2:
4.6 标识符、关键字、保留字
标识符
关键字
定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
特点:关键字中所有字母都为小写
保留字
Java保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用。自己命名标识符时要避免使用这些保留字byValue、cast、future、generic、 inner、operator、 outer、rest、var 、goto 、 const
。
4.7 进制
进制转换详解:
待续...
4.8 补码、反码和位运算
待续...
第3章 程序控制结构
三大流程控制语句: (1) 顺序控制 (2) 分支控制 (3) 循环控制
待续....
第4章 数组、排序和查找
1、为什么需要数组
一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。请问这六只鸡的总体重是多少?平均体重是多少? 请你编一个程序。
思路:
定义 6 个变量 , 加起来 总体重, 求出平均体重.引出 -> 数组
2、数组介绍
数组可以存放多个同一类型的数据。数组也是一种数据类型,是引用类型。即:数(数据)组(一组)就是一组数据
数组快速入门体验:
public class Array01{
public static void main(String[] args) {
/*
它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。
请问这六只鸡的总体重是多少?平均体重是多少?
思路分析
1. 定义六个变量 double , 求和 得到总体重
2. 平均体重 = 总体重 / 6
3. 分析传统实现的方式问题. 6->600->566
4. 引出新的技术 -> 使用数组来解决.
*/
// double hen1 = 3;
// double hen2 = 5;
// double hen3 = 1;
// double hen4 = 3.4;
// double hen5 = 2;
// double hen6 = 50;
// double totalWeight = hen1 + hen2 + hen3 + hen4 + hen5 + hen6;
// double avgWeight = totalWeight / 6;
// System.out.println("总体重=" + totalWeight
// + "平均体重=" + avgWeight);
// 比如,我们可以用数组来解决上一个问题 => 体验
// 定义一个数组
// 解读
// 1. double[] 表示 是 double 类型的数组, 数组名 hens
// 2. {3, 5, 1, 3.4, 2, 50} 表示数组的值/元素,依次表示数组的
// 第几个元素
double[] hens = {3, 5, 1, 3.4, 2, 50, 7.8, 88.8, 1.1, 5.6, 100};
// 遍历数组得到数组的所有元素的和, 使用 for
// 解读
// 1. 我们可以通过 hens[下标] 来访问数组的元素
// 下标是从 0 开始编号的比如第一个元素就是 hens[0]
// 第 2 个元素就是 hens[1] , 依次类推
// 2. 通过 for 就可以循环的访问 数组的元素/值
// 3. 使用一个变量 totalWeight 将各个元素累积
System.out.println("===使用数组解决===");
// 提示: 可以通过 数组名.length 得到数组的大小/长度
// System.out.println("数组的长度=" + hens.length);
double totalWeight = 0;
for (int i = 0; i < hens.length; i++) {
// System.out.println("第" + (i+1) + "个元素的值=" + hens[i]);
totalWeight += hens[i];
}
System.out.println("总体重=" + totalWeight + "平均体重=" + (totalWeight / hens.length));
}
}
3、数组的使用
3.1 使用方式1——动态初始化
先声明数组
语法:数据类型 数组名[ ]; 也可以 数据类型[ ] 数组名;
int a[ ]; 或者 int[ ] a;
创建数组
语法: 数组名=new 数据类型[大小];
a=new int[10];
import java.util.Scanner;
public class Array02{
public static void main(String[] args) {
// 演示 数据类型 数组名[]=new 数据类型[大小]
// 循环输入 5 个成绩,保存到 double 数组,并输出
// 步骤
// 1. 创建一个 double 数组,大小 5
//(1) 第一种动态分配方式
// double scores[] = new double[5];
//(2) 第 2 种动态分配方式, 先声明数组,再 new 分配空间
double scores[]; // 声明数组, 这时 scores 是 null
scores = new double[5]; // 分配内存空间,可以存放数据
// 2. 循环输入
// scores.length 表示数组的大小/长度
//
Scanner myScanner = new Scanner(System.in);
for (int i = 0; i < scores.length; i++) {
System.out.println("请输入第" + (i + 1) + "个元素的值");
scores[i] = myScanner.nextDouble();
}
// 输出,遍历数组
System.out.println("======数组的元素/值的情况如下:=======");
for (int i = 0; i < scores.length; i++) {
System.out.println("第" + (i + 1) + "个元素的值=" + scores[i]);
}
}
}
3.2 使用方式2——静态初始化
3.3 数组使用注意事项和细节
- 数组是多个相同类型数据的组合,实现对这些数据的统一管理
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值
int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null - 使用数组的步骤
- 声明数组并开辟空间
- 给数组各个元素赋值
- 使用数组
- 数组的
下标是从 0 开始的
。 - 数组下标必须在指定范围内使用,否则报:下标越界异常,比如:
int [] arr=new int[5]; 则有效下标为 0-4 - 数组属引用类型,数组型数据是对象(object)
public class ArrayDetail {
public static void main(String[] args) {
//1. 数组是多个相同类型数据的组合,实现对这些数据的统一管理
//int[] arr1 = {1, 2, 3, 60,"hello"};//String ->int
double[] arr2 = {1.1, 2.2, 3.3, 60.6, 100};//int ->double
//2. 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
String[] arr3 = {"北京","jack","milan"};
//3. 数组创建后,如果没有赋值,有默认值
//int 0,short 0, byte 0, long 0,
//float 0.0,double 0.0,char \u0000,
//boolean false,String null
short[] arr4 = new short[3];
System.out.println("=====数组 arr4=====");
for(int i = 0; i < arr4.length; i++) {
System.out.println(arr4[i]);
}
//6. 数组下标必须在指定范围内使用,否则报:下标越界异常,比如
//int [] arr=new int[5]; 则有效下标为 0-4
//即数组的下标/索引 最小 0 最大 数组长度-1(4)
int [] arr = new int[5];
//System.out.println(arr[5]);//数组越界
}
}
import java.util.Scanner;
//一维数组
public class Array1 {
public static void main(String[] args) {
//使用方式1:静态初始化
double[] arr = {12, 23, 34, 15.5, 66.66};
for (int i = 0; i < arr.length; i++) {
System.out.println("第" + (i + 1) + "个元素的值为:" + arr[i]);
}
//使用方式2:动态初始化
int[] arr1 = new int[6];
Scanner myScanner = new Scanner(System.in);
for (int i = 0; i < arr1.length; i++) {
System.out.println("请输入数组的"+(i+1)+"元素的值:");
arr1[i] = myScanner.nextInt();
}
System.out.println("输入完毕:"+arr1);
for (int i = 0; i < arr1.length; i++) {
System.out.println("第" + (i + 1) + "个元素的值为:" + arr1[i]);
}
}
}
4、数组应用案例
(1)创建一个 char 类型的 26 个元素的数组,分别 放置’A’-‘Z’。使用 for 循环访问所有元素并打印出来。提示:char 类型数据运算 ‘A’+2 -> ‘C’
public class ArrayExercise01 {
public static void main(String[] args) {
/*
思路分析
1. 定义一个 数组 char[] chars = new char[26]
2. 因为 'A' + 1 = 'B' 类推,所以老师使用 for 来赋值
3. 使用 for 循环访问所有元素
*/
char[] chars = new char[26];
for( int i = 0; i < chars.length; i++) {//循环 26 次
//chars 是 char[]
//chars[i] 是 char
chars[i] = (char)('A' + i); //'A' + i 是 int , 需要强制转换
}
//循环输出
System.out.println("===chars 数组===");
for( int i = 0; i < chars.length; i++) {//循环 26 次
System.out.print(chars[i] + " ");
}
}
}
(2) 请求出一个数组 int[]的最大值 {4,-1,9, 10,23},并得到对应的下标。
public class ArrayExercise02 {
public static void main(String[] args) {
//思路分析
//1. 定义一个 int 数组 int[] arr = {4,-1,9, 10,23};
//2. 假定 max = arr[0] 是最大值 , maxIndex=0;
//3. 从下标 1 开始遍历 arr, 如果 max < 当前元素,说明 max 不是真正的
// 最大值, 我们就 max=当前元素; maxIndex=当前元素下标
//4. 当我们遍历这个数组 arr 后 , max 就是真正的最大值,maxIndex 最大值
// 对应的下标
int[] arr = {4,-1,9,10,23};
int max = arr[0];//假定第一个元素就是最大值
int maxIndex = 0; //
for(int i = 1; i < arr.length; i++) {//从下标 1 开始遍历 arr
if(max < arr[i]) {//如果 max < 当前元素
max = arr[i]; //把 max 设置成 当前元素
maxIndex = i;
}
}
//当我们遍历这个数组 arr 后 , max 就是真正的最大值,maxIndex 最大值下标
System.out.println("max=" + max + " maxIndex=" + maxIndex);
}
}
5、数组赋值机制
- 基本数据类型赋值,这个值就是具体的数据,而且相互不影响。
int n1 = 2; int n2 = n1; - 数组在默认情况下是引用传递,赋的值是地址。
看一个案例,并分析数组赋值的内存图(重点, 难点
)。
int[] arr1 = {1,2,3};
int[] arr2 = arr1;
6、数组拷贝
public class ArrayCopy {
public static void main(String[] args) {
//案例:将 int[] arr1 = {10,20,30}; 拷贝到 arr2 数组,
//要求数据空间是独立的.
int[] arr1 = {10,20,30};
//创建一个新的数组 arr2,开辟新的数据空间
//大小 arr1.length;
int[] arr2 = new int[arr1.length];
//遍历 arr1 ,把每个元素拷贝到 arr2 对应的元素位置
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
//修改 arr2, 不会对 arr1 有影响.
arr2[0] = 100;
//输出 arr1
System.out.println("====arr1 的元素====");
for(int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);//10,20,30
}
System.out.println("====arr2 的元素====");
for(int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//
}
}
}
7、数组反转
要求:把数组的元素内容反转。
arr {11,22,33,44,55,66} {66, 55,44,33,22,11}
方式 1:通过找规律反转
public class ArrayReverse {
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55, 66};
//规律
//1. 把 arr[0] 和 arr[5] 进行交换 {66,22,33,44,55,11}
//2. 把 arr[1] 和 arr[4] 进行交换 {66,55,33,44,22,11}
//3. 把 arr[2] 和 arr[3] 进行交换 {66,55,44,33,22,11}
//4. 一共要交换 3 次 = arr.length / 2
//5. 每次交换时,对应的下标 是 arr[i] 和 arr[arr.length - 1 -i]
int temp = 0;
int len = arr.length; //计算数组的长度
for( int i = 0; i < len / 2; i++) {
temp = arr[len - 1 - i];//保存
arr[len - 1 - i] = arr[i];
arr[i] = temp;
}
System.out.println("===翻转后数组===");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");//66,55,44,33,22,11
}
}
}
方式 2:使用逆序赋值方式
public class ArrayReverse02 {
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55, 66};
//使用逆序赋值方式
//思路
//1. 先创建一个新的数组 arr2 ,大小 arr.length
//2. 逆序遍历 arr ,将 每个元素拷贝到 arr2 的元素中(顺序拷贝)
//3. 建议增加一个循环变量 j -> 0 -> 5
int[] arr2 = new int[arr.length];
//逆序遍历 arr
for(int i = arr.length - 1, j = 0; i >= 0; i--, j++) {
arr2[j] = arr[i];
}
//4. 当 for 循环结束,arr2 就是一个逆序的数组 {66, 55, 44,33, 22, 11}
//5. 让 arr 指向 arr2 数据空间, 此时 arr 原来的数据空间就没有变量引用
// 会被当做垃圾,销毁
arr = arr2;
System.out.println("====arr 的元素情况=====");
//6. 输出 arr 看看
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
8、数组添加/扩容
import java.util.Scanner;
public class ArrayAdd02 {
public static void main(String[] args) {
/*
要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
1.原始数组使用静态分配 int[] arr = {1,2,3}
2.增加的元素 4,直接放在数组的最后 arr = {1,2,3,4}
3.用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
思路分析
1. 定义初始数组 int[] arr = {1,2,3}//下标 0-2
2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
3. 遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
4. 将 4 赋给 arrNew[arrNew.length - 1] = 4;把 4 赋给 arrNew 最后一个元素
5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来 arr 数组就被销毁
6. 创建一个 Scanner 可以接受用户输入
7. 因为用户什么时候退出,不确定,老师使用 do-while + break 来控制
*/
Scanner myScanner = new Scanner(System.in);
//初始化数组
int[] arr = {1,2,3};
do {
int[] arrNew = new int[arr.length + 1];
//遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
for(int i = 0; i < arr.length; i++) {
arrNew[i] = arr[i];
}
System.out.println("请输入你要添加的元素");
int addNum = myScanner.nextInt();
//把 addNum 赋给 arrNew 最后一个元素
arrNew[arrNew.length - 1] = addNum;
//让 arr 指向 arrNew,
arr = arrNew;
//输出 arr 看看效果
System.out.println("====arr 扩容后元素情况====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//问用户是否继续
System.out.println("是否继续添加 y/n");
char key = myScanner.next().charAt(0);
if( key == 'n') { //如果输入 n ,就结束
break;
}
}while(true);
myScanner.close();
System.out.println("你退出了添加...");
}
}
9、排序的介绍
排序是将多个数据,依指定的顺序进行排列的过程。
排序的分类:
内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式排序法);
外部排序法:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。
10、 冒泡排序法
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
public class BubbleSort {
public static void main(String[] args) {
/**
数组 [24,69,80,57,13]
第 1 轮排序: 目标把最大数放在最后
第 1 次比较[24,69,80,57,13]
第 2 次比较[24,69,80,57,13]
第 3 次比较[24,69,57,80,13]
第 4 次比较[24,69,57,13,80]
*/
int[] arr = {24, 69, 80, 57, 13, -1, 30, 200, -110};
int temp = 0; //用于辅助交换的变量
//将多轮排序使用外层循环包括起来即可
//先死后活 => 4 就是 arr.length - 1
for( int i = 0; i < arr.length - 1; i++) {//外层循环是 4 次
for( int j = 0; j < arr.length - 1 - i; j++) {//4 次比较-3 次-2 次-1 次
//如果前面的数>后面的数,就交换
if(arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
System.out.println("\n==第"+(i+1)+"轮==");
for(int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + "\t");
}
}
}
}
11、 查找
11.1 介绍:
在 java 中,我们常用的查找有两种:
- 顺序查找
- 二分查找【二分法,我们放在算法讲解】
11.2 案例演示:
import java.util.Scanner;
public class SeqSearch {
public static void main(String[] args) {
/*
案例:有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王猜数游戏:
从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
要求: 如果找到了,就提示找到,并给出下标值
思路分析
1. 定义一个字符串数组
2. 接收用户输入, 遍历数组,逐一比较,如果有,则提示信息,并退出
*/
//定义一个字符串数组
String[] names = {"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"};
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入名字");
String findName = myScanner.next();
//遍历数组,逐一比较,如果有,则提示信息,并退出
int index = -1;
for(int i = 0; i < names.length; i++) {
//比较 字符串比较 equals, 如果要找到名字就是当前元素
if(findName.equals(names[i])) {
System.out.println("恭喜你找到 " + findName);
System.out.println("下标为= " + i);
//把 i 保存到 index
index = i;
break;//退出
}
}
if(index == -1) { //没有找到
System.out.println("sorry ,没有找到 " + findName);
}
}
}
12、 二维数组
连接
第5章 面向对象(初级)
1、认识对象和类
●概念关系
1)类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.
2)对象是具体的,实际的,代表一个具体事物, 即是实例.
3)类是对象的模板,对象是类的一个个体,对应一个实例
●对象的创建
1)先声明再创建
Cat cat ;//声明
cat = new Cat();//创建
2)直接创建
Cat cat = new Cat();
● Java对象内存的结构分析
1)栈:一般存放基本数据类型(局部变量)
2)堆:存放对象(Person person,数组等)
3)方法区:常量池(常量,比如字符串),类加载信息
对象在内存中存在形式:
2、属性(成员变量/字段)
●基本介绍
从概念或叫法上看:成员变量=属性=field(字段) (即成员变量是用来表示属性的)
- 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
有四种访问修饰符 public, proctected, 默认, private 。 - 属性的定义类型可以为任意类型,包含基本类型或引用类型
- 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,
boolean false,String null
3、方法(成员方法)
格式:
访问修饰符 返回数据类型 方法名(形参列表..){ //方法体
语句;
return返回值;
}
●解读:
1)形参列表:表示成员方法输入
2)返回数据类型:表示成员方法输出, void表示没有返回值
3)方法主体:表示为了实现某一功能代码块
4) return语句不是必须的。
调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
方法的调用执行机制:
4、成员方法传参机制和方法递归调用
待续。。。
5、方法重载(OverLoad)
●基本介绍
java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!比如: System.out.println(” );out是PrintStream类型
●重载的好处
1)减轻了起名的麻烦
2)减轻了记名的麻烦
●注意事项和使用细节
1)方法名:必须相同
2)形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
3)返回类型:无要求
public class MyOverLoad1 {
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
System.out.println(mc.calculate(1, 2));
System.out.println(mc.calculate(1.1, 2));
System.out.println(mc.calculate(1, 2.1));
}
}
class MyCalculator {
//下面的四个 calculate 方法构成了重载
//两个整数的和
public int calculate(int n1, int n2) {
System.out.println("calculate(int n1, int n2) 被调用");
return n1 + n2;
}
//错误
//public int calculate(int a1, int a2) {
// System.out.println("calculate(int a1, int a2) 被调用");
// return a1 + a2;
// }
//一个整数,一个 double的和
public double calculate(int n1, double n2) {
return n1 + n2;
}
//一个 double ,一个 Int的和
public double calculate(double n1, int n2) {
System.out.println("calculate(double n1, int n2) 被调用..");
return n1 + n2;
}
//三个 int 的和
public int calculate(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
}
6、可变参数
●基本概念
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现
●基本语法
访问修饰符 返回类型 方法名(数据类型…形参名){}
1)可变参数的实参可以为0个或任意多个
2)可变参数的实参可以为数组
3)可变参数的本质就是数组
4)可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
5)一个形参列表中只能出现一个可变参数
//可变参数
public class VarParamete {
public static void main(String[] args) {
ChangeParamete c =new ChangeParamete();
int total = c.sum(12,13,14,15);
System.out.println(total);
}
}
class ChangeParamete{
public int sum (int ...nums){
System.out.println("方法参数个数为:"+nums.length);
int res = 0;
for (int i = 0; i < nums.length; i++) {
res+=nums[i];
}
return res;
}
}
7、作用域
1.在java编程中,主要的变量就是属性(成员变量)和局部变量
。
2.的局部变量一般是指在成员方法中定义的变量
3.java中作用域的分类:
全局变量
:也就是属性,作用域为整个类体Cat类:cry eat等方法使用属性
局部变量:
也就是除了属性之外的其他变量,作用域为定义它的代码块中!
4.全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
●注意事项和细节使用
1.属性和局部变量可以重名,访问时遵循就近原则。
2.在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
3.属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。
即在一次方法调用过程中。
4.作用域范围不同
全局变量/属性:可以被本类使用,或其他类使用(通辽对家调用)局部变量:只能在本类中对应的方法中使用
5.修饰符不同
全局变量/属性可以加修饰符局部变量不可以加修饰符
构造方法/构造器
●基本语法:
[修饰符]方法名(形参列表){
方法体;
}
说明:
1)构造器的修饰符可以默认,也可以是 public protected private。
2)构造器没有返回值
3)方法名和类名字必须一样
4)参数列表和成员方法一样的规则
5)构造器的调用,由系统完成
●基本介绍
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:
1)方法名和类名相同
2)没有返回值
3)在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
构造器使用注意细节:
this
//this
public class This1 {
public static void main(String[] args) {
Dog dog1 = new Dog("大壮", 3);
System.out.println("dog1的hashcode=" + dog1.hashCode());
//dog1 调用了 info()方法
dog1.info();
System.out.println("============");
Dog dog2 = new Dog("大黄", 2);
System.out.println("dog2的hashcode=" + dog2.hashCode());
dog2.info();
}
}
class Dog { //类
String name;
int age;
//public Dog(String dName, int dAge) {//构造器
// name = dName;
// age = dAge;
// }
//如果我们构造器的形参,能够直接写成属性名,就更好了
//但是出现了一个问题,根据变量的作用域原则
//构造器的 name 是局部变量,而不是属性
//构造器的 age 是局部变量,而不是属性
//==> 引出 this 关键字来解决
public Dog(String name, int age) {//构造器
//this.name 就是当前对象的属性 name
this.name = name;
//this.age 就是当前对象的属性 age
this.age = age;
System.out.println("this.hashCode=" + this.hashCode());
}
public void info() {//成员方法,输出属性 x 信息
System.out.println("this.hashCode=" + this.hashCode());
System.out.println(name + "\t" + age + "\t");
}
}
1) this 关键字可以用来访问本类的属性、方法、构造器
2) this 用于区分当前类的属性和局部变量
3) 访问成员方法的语法:this.方法名(参数列表);
4) 访问构造器语法:this(参数列表);
注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)
5) this 不能在类定义的外部使用,只能在类定义的方法中使用
第5章 面向对象(中级)
1、IDEA(集成开发环境安装)
待续...
2、包
●包的三大作用
1.区分相同名字的类
2.当类很多时,可以很好的管理类[看Java API文档]
3.控制访问范围
●包基本语法
package com.code;说明:
1. package关键字 表示打包
2. com.code:表示包名
●命名规则:
只能包含数字、字母、下划线、小圆点…但不能用数字开头,不能是关键字或保留字
●常用的包
一个包下,包含很多的类,java 中常用的包有:
1) java.lang.* //lang 包是基本包,默认引入,不需要再引入.
2) java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
3) java.net.* //网络包,网络开发
4) java.awt.* //是做 java 的界面开发,GUI
com.code.pKg: import011.java
//语法:import 包;
//我们引入一个包的主要目的是要使用该包下的类
//比如import java.util.Scanner;就只是引入一个类Scanner。
import java.util.*;//表示将java.util包所有都引入
package hsp.base;
//package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面
//一个类中最多只有一句package
import java.util.Arrays;//Arrays包
import java.util.Scanner;//Scanner包
public class Pkg1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double[] arr={12.3,22,33,44,5,-20};
Arrays.sort(arr);//Arrays一个排序方法
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
2. import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
`
3、访问修饰符
3.1基本介绍
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)
1)公开级别:用public修饰,对外公开
2)受保护级别:用protected修饰,对子类和同一个包中的类公开
3)默认级别:没有修饰符号,向同一个包的类公开
4)私有级别:用private修饰,只有类本身可以访问,不对外公开
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | ✅ | ✅ | ✅ | ✅ |
受保护 | protected | ✅ | ✅ | ✅ | ❌ |
默认 | 无修饰符 | ✅ | ✅ | ❌ | ❌ |
私有 | private | ✅ | ❌ | ❌ | ❌ |
package hsp.pack1;
//A.java
public class A {
public int n1 = 100;
protected String n2 = "code";
double n3 = 30.5;
private boolean n4 = false;
public void m1() { //在同一类中,可以访问 public protected 默认 private 修饰属性和方法
System.out.println("n1=" + n1 + " n2=" + n2 + " n3=" + n3 + " n4=" + n4);
}
protected void m2() {
}
void m3() {
}
private void m4() {
}
public void hi() {
//在同一类中,可以访问 public protected 默认 private 修饰属性和方法
m1();
m2();
m3();
m4();
}
}
//
package hsp.pack1;
//B.java
public class B {
public void say() {
A a = new A();
//在同一个包下,可以访问 public , protected 和 默认修饰属性或方法,不能访问 private 属性或方法
System.out.println("n1=" + a.n1 + " n2=" + a.n2 + " n3=" + a.n3 + " n4=XXX");
a.m1();
a.m2();
a.m3();
//a.m4(); 错误的
}
}
//
package hsp.pack1;
//TestMain .java
public class TestMain {
public static void main(String[] args) {
A a = new A();
a.m1();
B b = new B();
b.say();
}
}
class Person{}
●使用的注意事项:
1)修饰符可以用来修饰类中的属性,成员方法以及类
2)只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
3)成员方法的访问规则和属性完全一样.
4、面向对象编程三大特征
封装、继承和多态
4.1 封装
封装
(encapsulation)就是把抽象出的数据[属性]
和对数据的操作[方法]
封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法]
,才能对数据进行操作。
封装的实现步骤 (三步)
1)将属性进行私有化private【不能直接修改属性】
2)提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx (类型参数名) {//Xxx表示某个属性
//加入数据验证的业务逻辑
属性=参数名;
}
3)提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxxO{ //权限判断,Xxx某个属性
return XX;
}
package hsp.base;
public class Encapsulation1 {
public static void main(String[] args) {
Person person = new Person("codeSE", "1234", 1000);
person.getPersonalInfo();
person.setName("Green");
person.setPws("123456");
person.setSalary(20000);
person.getPersonalInfo();
}
}
class Person {
public String name;//公开
private String pws;//私有
private double salary;//私有
public Person() {
}
public Person(String name, String pws, double salary) {
//在构造器可以调用方法,做限制
setName(name);
setPws(pws);
setSalary(salary);
}
public void setName(String name) {
this.name = name;
}
public void setPws(String pws) {
if (pws.length() == 6) {
this.pws = pws;
System.out.println("密码正确");
} else {
System.out.println("密码错误:必须六位数,默认111111");
this.pws = "111111";
}
}
public void setSalary(double salary) {
if (salary >= 10000) {
this.salary = salary;
System.out.println("工资过万,直接设置");
} else {
System.out.println("工资必须过万,默认10000元");
this.salary = 10000;
}
}
public String getName() {
return name;
}
public String getPws() {
return pws;
}
public double getSalary() {
return salary;
}
public void getPersonalInfo() {
System.out.println("获取信息:" + getName() + "\t" + getPws() + "\t" + getSalary());
}
}
4.2 继承
4.2.1 概念、语法
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中
抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends 来声明继承父类即可。
● 继承的基本语法
子类 extends 父类{
}
1)子类就会自动拥有父类定义的属性和方法
2)父类又叫超类,基类。
3)子类又叫派生类。
public class Personal {
String name;
int age;
double height;
//公共方法
public void setAge(int age){
this.age = age;
}
public String showInfo(){
return "姓名:"+name+"年龄:"+age+"身高:"+this.height;
}
}
//
public class Xiaoming extends Personal{
public void test(){
System.out.println(name+"输出了!");
}
}
//
public class Xiaohua extends Personal{
public void test(){
System.out.println(name+"输出了!");
}
}
//
public class TestMain {
public static void main(String[] args) {
Xiaoming xiaoming = new Xiaoming();
xiaoming.name = "小明";
xiaoming.height = 175;
xiaoming.setAge(20);
String info1= xiaoming.showInfo();
System.out.println(info1);
xiaoming.test();
System.out.println("========================================================================");
Xiaohua xiaohua = new Xiaohua();
xiaohua.name="小花";
xiaoming.height = 170;
xiaohua.setAge(18);
String info2 = xiaohua.showInfo();
System.out.println(info2);
xiaohua.test();
}
}
4.2.2 继承在构造函数中的使用
public class Father {
public int n1= 10;
protected double n2 = 23.2;
boolean n3 = false;
private String n4 = "code";
//public Father(){
// System.out.println("父类Father()无参构造器被调用....");
//}
public Father(int n1, String n4) {
this.n1 = n1;
this.n4 = n4;
System.out.println("父类Father(int n1, String n4)有参构造器被调用....");
}
}
//
public class Son extends Father{
int s1 = 200;
public Son(){
//super();
//1、如果什么都不写,默认调用就是super(),即:调用父类无参构造器
//2、写了super(参数),才是指定了父类的构造器
super(1000,"VB");//指定父类构造器
System.out.println("子类Son()无参构造器被调用....");
}
public Son(int s1){
super(2000,"CB");//指定父类构造器
this.s1 = s1;
System.out.println("子类Son(int s1)有参构造器被调用....");
}
//主函数
public static void main(String[] args) {
Son son1 = new Son();//默认都会调父类Father()无参构造器 //但是父类没有无参构造器,必须用super来指定
Son son2 = new Son(100);//默认都会调父类Father()无参构造器 //但是父类没有无参构造器,必须用super来指定
/*
* 父类Father()无参构造器被调用....
* 子类Son()无参构造器被调用....
* 父类Father()无参构造器被调用....
* 子类Son(int s1)有参构造器被调用....
* */
/*
* 父类Father(int n1, String n4)有参构造器被调用....
* 子类Son()无参构造器被调用....
* 父类Father(int n1, String n4)有参构造器被调用....
* 子类Son(int s1)有参构造器被调用....
* */
}
}
1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,
但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,
如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,
否则,编译不会通过。
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行 (super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) java 所有类都是 Object 类的子类, Object 是所有类的基类.
8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9) 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
4.2.3 继承关系本质理解
public class ExtendTheory {
public static void main(String[] args) {
Son1 son = new Son1();//内存的布局
// (1)首先看子类是否有该属性
// (2)如果子类有这个属性,并且可以访问,则返回信息
// (3)如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
// (4)如果父类没有就按照(3)的规则,继续找上级父类,直到Object...
System.out.println(son.name);//返回就是大头儿子
System.out.println(son.age);//返回的就是39
System.out.println(son.hobby);//返回的就是旅游
//注意:如果向上访问遇到私有的属性(方法),即使还有也会终止查找
}
}
class GrandPa1 {//爷类
String e = "大头爷爷";
String hobby = "旅游";
}
class Father1 extends GrandPa1 {//父类
String name = "大头爸爸";
int age = 39;
}
class Son1 extends Father1 {//子类
String name = "大头儿子";
}
4.2.4 super
super 代表父类的引用,用于访问父类的属性、方法、构造器
1.访问父类的属性,但不能访问父类的private属性
super.属性名;
2.访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
3.访问父类的构造器(这点前面用过)
super(参数列表);只能放在构造器的第一句,只能出现一句!
package hsp.extend.supers;
public class Base {
public int n1 = 999;
public int age = 111;
public void cal() {
System.out.println("Base 类的 cal() 方法...");
}
public void eat() {
System.out.println("Base 类的 eat().....");
}
}
//
package hsp.extend.supers;
public class A extends Base {
//4 个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public A() {
}
public A(String name) {
}
public A(String name, int age) {
}
public void cal() {
System.out.println("A 类的 cal() 方法...");
}
public void test100() {
}
protected void test200() {
}
void test300() {
}
private void test400() {
}
}
//
package hsp.extend.supers;
public class B extends A {
public int n1 = 888;
public void test() {
//super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员;
// 如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C
System.out.println("super.n1=" + super.n1);
super.cal();
}
//访问父类的属性 , 但不能访问父类的 private 属性 [案例]super.属性名
public void hi() {
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
public void cal() {
System.out.println("B 类的 cal() 方法...");
}
public void sum() {
System.out.println("B 类的 sum()");
//希望调用父类-A 的 cal 方法,这时,因为子类 B 没有 cal 方法,因此我可以使用下面三种方式
// 找 cal 方法时(cal() 和 this.cal()),顺序是:
// (1)先找本类,如果有,则调用
// (2)如果没有,则找父类(如果有,并可以调用,则调用)
// (3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类
// 提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
// 如果查找方法的过程中,没有找到,则提示方法不存在
// cal();
this.cal(); //等价 cal
// 找 cal 方法(super.call()) 的顺序是直接查找父类,其他的规则一样
super.cal();//跳过本类,
//n1 和 this.n1 查找的规则是
//(1) 先找本类,如果有,则调用
// (2) 如果没有,则找父类(如果有,并可以调用,则调用)
// (3) 如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类
// 提示:如果查找属性的过程中,找到了,但是不能访问, 则报错, cannot access
// 如果查找属性的过程中,没有找到,则提示属性不存在
System.out.println(n1);
System.out.println(this.n1); //找 n1 (super.n1) 的顺序是直接查找父类属性,其他的规则一样
System.out.println(super.n1);
}
//访问父类的方法,不能访问父类的 private 方法: super.方法名(参数列表);
void ok() {
super.test100();
super.test200();
super.test300();
//super.test400();//不能访问父类 private 方法
}
//访问父类的构造器:super(参数列表);只能放在构造器的第一句,只能出现一句!
public B() {
//super();
super("jack", 10);
//super("jack");
}
}
//
package hsp.extend.supers;
public class TestMain1 {
public static void main(String[] args) {
B b = new B();//子类对象
b.sum();
b.test();
}
}
super 给编程带来的优点/细节
1、调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。
如果没有重名,使用super、this、直接访问是一样的效果!
3. super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,
也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,
使用super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则
super 和 this 的比较
4.2.5 方法重写/覆盖(override)
简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法
package hsp.extend.overirde;
public class Animal {
public void running(){
System.out.println("动物在跑");
}
public Object eat(){
return null;
}
public String sleep(){
return "睡觉";
}
public void swimming(){
}
}
//
package hsp.extend.overirde;
public class Dog extends Animal {
String name = "dog";
public void running() {//重写running方法
System.out.println(this.name + "在跑");
}
public String eat() {//重写eat方法
return null;
}
//编译报错,返回类型大于父类
// public Object sleep(){
// return "睡觉";
// }
//编译报错:缩小了父类的方法权限范围
// protected void swimming(){
//
// }
}
//
package hsp.extend.overirde;
public class TestMain {
public static void main(String[] args) {
Dog dog = new Dog();
dog.running();
}
}
注意事项和使用细节
方法重写也叫方法覆盖,需要满足下面件:
1.子类的方法的参数方法名称,要和父类方法的参数方法名称【完全一样】。
2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子比如父类返回类型是 Object ,子类方法返回类型是String
public object getInfo(){} public string getInfo(){} //ok可以
3.子类方法不能缩小父类方法的访问权限:public > protected >默认>private
void sayOk(){ } public void sayok(){ } //err 不行
4.3 多态
4.3.1 方法或对象多种形态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
先来一段代码预热:
package hsp.ploy;
public class AB {
public static void main(String[] args) {
A a = new A();
B b = new B();
//方法重载体现多态
b.sum(11,22);
b.sum(11,22,33);
//方法重写体现多态
a.say();
b.say();
}
}
class A {
public void say() {
System.out.println("父类方法");
}
}
class B extends A {
public void say() {
System.out.println("子类方法");
}
public int sum(int a, int b) {
return a + b;
}
public int sum(int a, int b, int c) {
return a + b + c;
}
}
4.3.2 对象的多态 (核心,难点,重点
)
(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的.
(4)编译类型看定义时=号的左边,运行类型看=号的右边
Animal animal = new Dog; 【animal编译类型是Animal,运行类型Dog】
animal = new Cat();【animal的运行类型变成了Cat,编译类型仍然是 Animal】
●●什么是多态,多态具体体现有哪些?●●
多态:方法或对象具有多种形态,是OOP的第三大特征,是建立在封装和继承基础之上
●●多态具体体现:●●
1.方法多态
(1)重载体现多态
(2)重写体现多态
2.对象多态
(1)对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
(2)对象的运行类型是可以变化的,可以通过getClasss()来查看运行类型
(3)编译类型看定时时=号的左边,运行类型看=号右边
package hsp.ploy;
public class Animal {
public void running(){
System.out.println("动物在跑");
}
}
//
public class Dog extends Animal {
public void running() {
System.out.println("狗在吠!");
}
}
//
package hsp.ploy;
public class Cat extends Animal{
public void running() {
System.out.println("猫在叫!");
}
}
//
package hsp.ploy;
public class TestMain {
public static void main(String[] args) {
//体验对象多态特点
// animal 编译类型就是 Animal , 运行类型 Dog
Animal animal = new Dog(); //因为运行时,执行到该行时,animal 运行类型是 Dog,所以 running就是 Dog 的 running
animal.running(); //狗在吠!
// animal 编译类型 Animal,运行类型就是 Cat
animal = new Cat();
animal.running(); //猫在叫
}
}
4.3.3 多态注意事项和细节
✔️●多态的前提是: 两个对象(类)存在继承关系
√多态的向上转型
1)本质:父类的引用指向了子类的对象
2)语法:父类类型 引用名= new子类类型();
3)特点:编译类型看左边,运行类型看右边。可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;最终运行效果看子类的具体实现!
√多态向下转型
1)语法:子类类型 引用名 = (子类类型) 父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象
4)当向下转型后,可以调用子类类型中所有的成员
package hsp.ploy1;
public class Animal {
String name = "动物";
int age = 10;
public void running(){
System.out.println("动物在跑");
}
public void eating(){
System.out.println("动物在吃");
}
}
//
package hsp.ploy1;
public class Cat extends Animal {
String name = "猫";
public void eating() {//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse() {//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
//
package hsp.ploy1;
public class TestMain {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类的对象
//语法:父类类型引用名=new子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗?可以Object也是Cat的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类的特有的成员
//(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
//(4)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用。
// animal.catchMouse();错误
animal.eating();//猫吃鱼
animal.running();//动物在跑!
//多态的向下转型
//(1)语法:子类类型引用名=(子类类型)父类引用;
Cat cat = (Cat) animal;//cat的编译类型Cat,运行类型是Cat
cat.catchMouse();//猫抓老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog) animal;//不行,相当于猫强制转为狗,两者没有关系
//属性没有重写之说!属性的值看编译类型
Animal a1 = new Cat();//向上转型
System.out.println(a1.name);//动物
Cat c1 = new Cat();
System.out.println(c1.name);//猫
//instanceOf比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
Cat c2 = new Cat();
System.out.println(c2 instanceof Cat);//true
System.out.println(c2 instanceof Animal);//true
//aa编译类型Animal,运行类型是Cat
// Cat是Animal子类
Animal aa = new Cat();
System.out.println(aa instanceof Animal);//true
System.out.println(aa instanceof Cat);//true
Object obj1 = new Object();
System.out.println(obj1 instanceof Animal);//false
String str = "hello";
//System.out.println(str instanceof Animal);//错误
System.out.println(str instanceof Object);//true
}
}
4.3.4 java的动态绑定机制(非常非常重要
)
package hsp.ploy1;
public class AB {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//?40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}
class A {
public int i = 10;
//动态绑定机制:
public int sum() {//父类 sum()
return getI() + 10;//20 + 10 方法动态绑定,去调了子类
}
public int sum1() {//父类 sum1()
return i + 10;//10 + 10 属性无动态绑定机制
}
public int getI() {//父类 getI
return i;
}
}
class B extends A {
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子类 getI()
return i;
}
// public int sum1() {
//return i +10;
// }
}
4.3.5 多态的应用
- 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
package hsp.ploy2;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String say() {
return name + "\t" + age;
}
}
//
package hsp.ploy2;
public class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重写父类
@Override
public String say() {
return "学生 " + super.say() + " score=" + score;
}
//特有的方法
public void study() {
System.out.println("学生 " + getName() + " 正在学 java...");
}
}
//
package hsp.ploy2;
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//重写父类
@Override
public String say() {
return "老师 " + super.say() + " salary=" + salary;
}
//特有方法
public void teach() {
System.out.println("老师 " + getName() + " 正在讲 java 课程...");
}
}
//
package hsp.ploy2;
//多态数组
public class PloyArray {
public static void main(String[] args) {
//应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//循环遍历多态数组,调用say()方法
for (int i = 0; i < persons.length; i++) {
String str = persons[i].say();//动态绑定机制:persons[i]编译类型,运行类型根据JVM判断
System.out.println(str);
if(persons[i] instanceof Student){
Student student = (Student) persons[i]; //向下转型
student.study();
} else if (persons[i] instanceof Teacher) {
Teacher teacher = (Teacher) persons[i]; //向下转型
teacher.teach();
}else if (persons[i] instanceof Person) {
} else {
System.out.println("类型错误,检查...");
}
}
}
}
- 多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
package hsp.ploy3;
//员工类
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getMoney(){
return 12 * this.salary;
}
}
//
package hsp.ploy3;
//普通员工类
public class Worker extends Employee{
public Worker(String name, double salary) {
super(name, salary);
}
@Override
public double getMoney() {
return super.getMoney();
}
public void work() { //普通员工没有其它收入,则直接调用父类方法
System.out.println("普通员工 " + getName() + " is working");
}
}
//
package hsp.ploy3;
//经理类
public class Manger extends Employee{
private double bonus;//奖金
public Manger(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public double getMoney() {
return super.getMoney() + this.bonus;
}
public void manage() {
System.out.println("经理 " + getName() + " is managing");
}
}
//
package hsp.ploy3;
public class TestMain {
public static void main(String[] args) {
Worker green = new Worker("Green", 3000);
Manger tim = new Manger("Tim", 6000, 100000);
TestMain TestMain = new TestMain();
TestMain.showEmpAnnual(green);//36000.0
TestMain.showEmpAnnual(tim);//172000.0
TestMain.testWork(green);//普通员工 Green is working
TestMain.testWork(tim);//经理 Tim is managing
}
//showEmpAnnual(Employee e)
//实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [e.getAnnual()]
public void showEmpAnnual(Employee e) {
System.out.println(e.getMoney());//动态绑定机制. }
}
//添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
public void testWork(Employee e) {
if (e instanceof Worker) {
((Worker) e).work();//有向下转型操作
} else if (e instanceof Manger) {
((Manger) e).manage();//有向下转型操作
} else {
System.out.println("不做处理...");
}
}
}
5.Object 类详解
5.1 equals 方法
== 是一个比较运算符
1. ==: 既可以判断基本类型,又可以判断引用类型
2. ==: 如果判断基本类型,判断的是值是否相等。示例:int i=10; double d=10.0;
3. ==: 如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
4. equals:是Object类中的方法,只能判断引用类型
5.默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如lnteger,String【看看String 和 Integer的equals源代码】
package hsp.object1;
public class Equals1 {
public static void main(String[] args) {
A a = new A();
A b = a;
A c = b;
System.out.println(a == c);//true 同一对象
System.out.println(b == c);//true 同一对象
B bObj = a;
System.out.println(bObj == c);//true
int num1 = 10;
double num2 = 10.0;
System.out.println(num1 == num2);//true //基本数据类型,判断值是否相等
/*
equals源码
//把 Object 的 equals 方法重写了,变成了比较两个字符串值是否相同
public boolean equals(Object anObject) {
if (this == anObject) { //如果是同一个对象
return true;
}
if (anObject instanceof String) { //判断类型
String anotherString = (String)anObject; //向下转型
int n = value.length;
if (n == anotherString.value.length) { //如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) //然后一个一个的比较字符
return false;
i++;
}
return true;//如果两个字符串的所有字符都相等,则返回 true
}
}
return false;//不是是同一个对象,直接false
}
*/
/* Object 类的 equals
//即 Object 的 equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象.
public boolean equals(Object obj) {
return (this == obj);
}
*/
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false
System.out.println(integer1.equals(integer2));//true
String str1 = new String("hspedu");
String str2 = new String("hspedu");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
}
}
class B {}
class A extends B {}
重写equals方法
应用实例: 判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。
package hsp.object1;
public class Equals2 {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 10, '男');
System.out.println(person1.equals(person2));//true
Person_ p1 = new Person_();
p1.name = "codeSE";
Person_ p2 = new Person_();
p2.name = "codeSE";
System.out.println(p1==p2); //false
System.out.println(p1.name .equals(p2.name));//true
System.out.println(p1.equals(p2));//false
String s1 = new String("asdf");
String s2 = new String("asdf");
System.out.println(s1.equals(s2));//true
System.out.println(s1==s2); //false
int it = 65;
float fl = 65.0f;
System.out.println("65 和 65.0f 是否相等?" + (it == fl));//true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65 和‘A’是否相等?" + (it == ch1));//true
System.out.println("12 和 ch2 是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //false
System.out.println("str1 是否 equals str2?"+(str1.equals(str2)));//false
// System.out.println("hello"== new java.sql.Date()); //编译错误
}
}
class Person {
private String name;
private int age;
private char gender;
@Override //重写 Object 的 equals 方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 true
if (this == obj) {
return true;
}
//类型判断
if (obj instanceof Person) {//是 Person,我们才比较
Person p = (Person) obj;//进行 向下转型, 因为我需要得到 obj 的 各个属性
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
return false; //如果不是 Person ,则直接返回 false
}
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
class Person_{
public String name;
}
5.2 hashCode 方法
1) 提高具有哈希结构的容器的效率!
2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3) 两个引用,如果指向的是不同对象,则哈希值是不一样的
package hsp.object1;
public class HashCode1 {
public static void main(String[] args) {
AA a1 = new AA();
AA a2 = new AA();
AA a3 = a1;
System.out.println("A.hashCode()=" + a1.hashCode());//10568834
System.out.println("A2.hashCode()=" + a2.hashCode());//21029277
System.out.println("A3.hashCode()=" + a3.hashCode());//10568834
}
}
class AA {}
5.3 toString 方法
- 基本介绍
默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】
子类往往重写 toString 方法,用于返回对象的属性信息 - 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式.
- 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用
monster.toString()
package hsp.object1;
public class ToString1 {
/*
Object 的 toString() 源码
(1)getClass().getName() 类的全类名(包名+类名 )
(2)Integer.toHexString(hashCode()) 将对象的 hashCode 值转成 16 进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
public static void main(String[] args) {
Monster monster = new Monster("蜘蛛精", "吐丝的", 1234.50);
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());//hsp.object1.Monster@a14482 hashcode=10568834
System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
System.out.println(monster); //等价 monster.toString() //hsp.object1.Monster@a14482
System.out.println("-------------重写toString方法后----------------------");
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());//Monster{name='蜘蛛精', work='吐丝的', count=1234.5} hashcode=10568834
System.out.println(monster); //Monster{name='蜘蛛精', work='吐丝的', count=1234.5}
}
}
class Monster{
private String name;
private String work;
private double count;
public Monster(String name, String work, double count) {
this.name = name;
this.work = work;
this.count = count;
}
//重写 toString 方法, 输出对象的属性
@Override
public String toString() { //重写后,一般是把对象的属性值输出,也可以自己定制
return "Monster{" +
"name='" + name + '\'' +
", work='" + work + '\'' +
", count=" + count +
'}';
}
}
5.4 finalize 方法
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来
销毁该对象,在销毁该对象前,会先调用 finalize 方法 - 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
package hsp.object1;
public class Finalize1 {
public static void main(String[] args) {
Ping ping = new Ping("小猪的食物");
ping = null;
//ping就变成一个垃圾,垃圾回收机制会回收(销毁)该对象,在销毁前会调用finalize方法,
// 所以可以在 finalize 中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
//如果不重写 finalize,那么就会调用 Object 类的 finalize, 即默认处理
System.gc();//主动调用垃圾回收器
System.out.println("退出了");
}
}
class Ping{
private String food;
public Ping(String food) {
this.food = food;
}
@Override //重写finalize
protected void finalize() throws Throwable {
System.out.println("销毁了"+this.food+"释放资源...");
}
}
6. 断点调试(debug)
断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
2.断点调试是程序员必须掌握的技能。
3.断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
● 断点调试的快捷键
F7(跳入)
F8(跳过)
shift+F8(跳出)
F9(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift+F8:跳出方法
第5章 面向对象(高级)
1、类变量和类方法
1.1 什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
1.2 定义类变量定义语法:
访问修饰符 static 数据类型 变量名;【推荐】
static 访问修饰符 数据类型 变量名;
1.3 如何访问类变量
类名.类变量名
对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性是一样的】
推荐使用:类名.类变量名
public class Static1 {
public static void main(String[] args) {
//类变量是随着类加载而创建,不用实例化可直接访问
System.out.println(A.name);//code SE
A a = new A();
System.out.println(a.name);//code SE
//System.out.println(A.salary);//错误
}
}
class A {
public double salary =4500;//普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
public static int count = 0; //静态属性/静态成员/静态成员变量
public static String name = "code SE";//类变量,必须遵守相关访问权限
}
●类变量使用注意事项和细节
1.什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时, 就可以考虑使用类变量(静态变量)。
2.类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
3.加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4.类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐
我们使用类名类变量名方式访问。【前提是 满足访问修饰符的访问权限和范围】
5.实例变量不能通过类名.类变量名方式访问。
6.类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,
就可以使用类变量了。
7.类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法
类方法也叫静态方法。
形式如下:
访问修饰符 static 数据返回类型 方法名(){ } 【推荐】
static 访问修饰符 数据返回类型 方法名(){ }
使用方式:
类名.类方法名或者对象名.类方法名 【前提是满足访问修饰符的访问权限】
package hsp.static1;
public class Static2 {
public static void main(String[] args) {
Student s1 = new Student("Green");
Student.total(4500);
//s1.total(4500);
Student s2 = new Student("Green");
Student.total(4500);
//s2.total(4500);
Student.showHeight();//所有人总身高:9000.0
double sum = MyUntil.sum(12.3,45.6);
System.out.println(sum);//57.900000000000006
}
}
class Student {
private String name;
private static double heights;
public Student(String name) {
this.name = name;
}
//static静态方法 可直接访问静态属性
public static void total(double height) {
Student.heights += height;
}
public static void showHeight(){
System.out.println("所有人总身高:"+Student.heights);
}
}
class MyUntil{
public static double sum(double n1,double n2){
return n1+n2;
}
}
类方法使用注意事项和细节
1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数
2)类方法可以通过类名调用,也可以通过对象名调用。
3)普通方法和对象有关,需要通过对象名调用,比如对象名方法名(参数),不能通过类名调用。
4)类方法中不允许使用和对象有关的关键字,比如this和super, 普通方法(成员方法)可以。
5)类方法(静态方法)中只能访问静态变量或静态方法。
6)普通成员方法,既可以访问非静态成员,也可以访问静态成员。
小结:
静态方法, 只能访问静态的成员,非静态的方法,可以访向静态成员和非静态成员(必须遵守访问权限)
package hsp.static1;
public class Static3 {
}
class AA{
private int n1 = 100;
private static int n2 = 1000;
//普通方法
public void run(){}
//静态方法
public static void eat(){
//静态方法中不允许使用和对象有关的关键字,比如 this 和 super。
//System.out.println(this.n1);//错
//静态方法中 只能访问 静态变量 或静态方法
//System.out.println(n1);//错
System.out.println(n2);
//run();//错
sleep();
}
static void sleep(){}
void jump(){
//普通成员方法,既可以访问 非静态成员,也可以访问静态成员
System.out.println(n1);
System.out.println(n2);
run();
sleep();
}
}
2、main方法
解释main方法的形式: public static void main(String[] args){ }
- main方法时虚拟机调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main(方法时不必创建对象, 所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所
运行的类的参数,接收参数 - java 执行的程序 参数1 参数2 参数3
特别提示:
1)在main(方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一一个实例对象后,才能通过这个对象去访问类中的非静
态成员
3、代码块
3.1基本介绍
代码化块又称为初始化块,属于类中的成员【即是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
3.2基本语法
[修饰符]{
代码
}
说明注意:
1)修饰符可选,要写的话,也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的, 叫普通代码块/非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);号可以写上,也可以省略。
3.3代码块的好处
1)相当于另外种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
package hsp.codeBlock;
public class CodeBlock01 {
public static void main(String[] args) {
Movie m1 = new Movie("花木兰");
Movie m2 = new Movie("长江七号",40.5);
Movie m3 = new Movie("哪吒",55,800);
/*输出结果如下:
* 即将开始......马上进入
* 花木兰
* 即将开始......马上进入
* 长江七号40.5
* 即将开始......马上进入
* 哪吒55.0800
* */
}
}
class Movie{
private String name;
private double price;
private int count;
//代码块 不管调用哪个构造器,创建对象,都会先调用代码块的内容
//代码块调用的顺序优先于构造器
{
System.out.println("即将开始......马上进入");
}
public Movie() {
}
public Movie(String name) {
this.name = name;
System.out.println(this.name);
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
System.out.println(this.name+this.price);
}
public Movie(String name, double price, int count) {
this.name = name;
this.price = price;
this.count = count;
System.out.println(this.name+this.price+ this.count);
}
}
3.4 代码块使用注意事项和细节
1) static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而
执行,并且只会执行一次。如果是普通代码块,每创建 一个对象,就执行。
2)类什么时候被加载【重要!】
①创建对象实例时(new)
②创建子类对象实例,父类也会被加载
③使用类的静态成员时(静态属性,静态方法)
案例演示: A类extends B类的静态块
3)普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用次。
如果只是使用类的静态成员时,普通代码块并不会执行。
小结:
1. static代码块是类加载时,执行,只会执行1次。
2. 普通代码块是在创建对象时调用的,每创建一次,就调用1次。
代码演示:
package hsp.codeBlock;
public class CodeBlock02 {
public static void main(String[] args) {
//类被加载的情况举例
//1. 创建对象实例时(new)
BB bb1 = new BB();
//2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
BB bb2 = new BB();
//3. 使用类的静态成员时(静态属性,静态方法),父类的静态代码块和子类的静态代码块会执行
//System.out.println(Cat.n1);
//static 代码块,是在类加载时,执行的,而且只会执行一次.
CC dd1 = new CC(); //CC 的静态代码 1 被执行... CC 的普通代码块...
CC dd2 = new CC(); // CC 的普通代码块...
//普通的代码块,在创建对象实例时,会被隐式的调用。
// 被创建一次,就会调用一次。
// 如果只是使用类的静态成员时,普通代码块并不会执行
System.out.println(CC.n1);//8888, 静态模块块一定会执行
}
}
class AA {
//静态代码块
static {
System.out.println("AA 的静态代码 1 被执行...");//
}
}
class BB extends AA {
//静态代码块
static {
System.out.println("BB 的静态代码 1 被执行...");//
}
}
class CC {
public static int n1 = 888888;//静态属性
//静态代码块
static {
System.out.println("CC 的静态代码 1 被执行...");//
}
//普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
{
System.out.println("CC 的普通代码块...");
}
}
class Animal {
//静态代码块
static {
System.out.println("Animal 的静态代码 1 被执行...");//
}
}
class Cat extends Animal {
public static int n1 = 9999;//静态属性
//静态代码块
static {
System.out.println("Cat 的静态代码 1 被执行...");//
}
}
4)创建一个对象时,在一个类调用顺序是:(重点,难点 ) :
①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,
如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,
如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③调用构造方法。
代码演示:
package hsp.codeBlock;
public class CodeBlock03 {
public static void main(String[] args) {
A a = new A();
/*
* (1) A 静态代码块
* (2) getN1 被调用...
* (3)A 普通代码块
* (4)getN2 被调用...
* (5)A() 构造器被调用
* */
}
}
class A {
{
System.out.println("A 普通代码块 01");
}
private int n2 = getN2();//普通属性的初始化
static { //静态代码块
System.out.println("A 静态代码块 01");
}
//静态属性的初始化
private static int n1 = getN1();
public static int getN1() {
System.out.println("getN1 被调用...");
return 100;
}
public int getN2() { //普通方法/非静态方法
System.out.println("getN2 被调用...");
return 200;
}
//无参构造器
public A() {
System.out.println("A() 构造器被调用");
}
}
5)构造器的最前面其实隐含了super(和调用普通代码块,静态相关的代码块,属性初始化,
在类加载时,就执行完毕因此是优先于构造器和普通代码块执行的
class A {
public A( {/构造器
//这里有隐藏的执行要求
//(1) super();
//(2)调用普通代码块的
System.out.println(" ok");
}
}
代码演示:
package hsp.codeBlock;
public class CodeBlock04 {
public static void main(String[] args) {
BBB bbb = new BBB();
/*
* AAA 的普通代码块
* AAA() 构造器被调用....
* BBB 的普通代码块...
* BBB() 构造器被调用....
* */
}
}
class AAA { //父类 Object
{
System.out.println("AAA 的普通代码块");
}
public AAA() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("AAA() 构造器被调用....");
}
}
class BBB extends AAA {
{
System.out.println("BBB 的普通代码块...");
}
public BBB() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("BBB() 构造器被调用....");
}
}
6)我们看一下创建一 个子类对象时(继承关系), 他们的静态代码块,静态属性初始化,普通代码块,
普通属性初始化,构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④父类的构造方法
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥子类的构造方法//面试题
7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
代码演示:
package hsp.codeBlock;
public class CodeBlock05 {
public static void main(String[] args) {
//(1) 进行类的加载
//1.1 先加载 父类 AAAA
// 1.2 再加载 BBBB
//(2) 创建对象
//2.1 从子类的构造器开始
new BBBB();
new CCCC();
}
}
class AAAA { //父类
private static int n1 = getVal01();
static {
System.out.println("AAAA 的一个静态代码块..");//(2)
}
{
System.out.println("AAAA 的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public AAAA() {//构造器
//隐藏
//super()
//普通代码和普通属性的初始化......
System.out.println("AAAA 的构造器");//(7)
}
}
class BBBB extends AAAA { //
private static int n3 = getVal03();
static {
System.out.println("BBBB 的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("BBBB 的第一个普通代码块..");//(9)
}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
public BBBB() {//构造器
//隐藏了
//super()
//普通代码块和普通属性的初始化...
System.out.println("BBBB 的构造器");//(10)
}
}
class CCCC {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}
static {
//静态代码块,只能调用静态成员
//System.out.println(n1);错误
System.out.println(n2);//ok
//m1();//错误
m2();
}
{
//普通代码块,可以使用任意成员
System.out.println(n1);
System.out.println(n2);//ok
m1();
m2();
}
}
4、单例模式
4.1 什么是单例模式
单例(单个的实例)
1.所谓类的单例设计模式,就是采取一 定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法
2.单例模式有两种方式: 1)饿汉式 2)懒汉式
4.2 单例模式应用实例
饿汉式和懒汉式单例模式的实现。
步骤如下:
1)构造器私有化 ==> 防止直接new
2)类的内部创建对象
3)向外暴露一一个静态的公共方法。getnstance
package hsp.singleModel;
//单例模式-饿汉式
public class Single01 {
public static void main(String[] args) {
// GirlFriend xh = new GirlFriend("小红");
// GirlFriend xb = new GirlFriend("小白");
//通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);//GirlFriend{name='玉玉'}
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);//GirlFriend{name='玉玉'}
System.out.println(instance == instance2);//true
}
}
class GirlFriend{
private String name;
//public static int n1 = 100;
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
//对象,通常是重量级的对象, 饿汉式可能造成創建了對象,但是沒有使用.
private static GirlFriend gf = new GirlFriend("玉玉");
//如何保障我们只能创建一个 GirlFriend 对象
//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是 static)
//3. 提供一个公共的 static 方法,返回 gf 对象
private GirlFriend(String name) {
System.out.println("构造器被调用...");
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
package hsp.singleModel;
public class Single02 {
public static void main(String[] args) {
//new Dog("大黃");
//System.out.println(Dog.n1);
Dog instance = Dog.getInstance();
System.out.println(instance);
//再次調用 getInstance
Dog instance2 = Dog.getInstance();
System.out.println(instance2);
System.out.println(instance ==instance2);
}
//希望程序执行过程中只能创建一次对象
class Dog {
private String name;
public static int n1 = 10;
private static Dog dog; //默认是 null, 【注意此处需要高版本的jdk才支持】
//步驟:
//1.仍然光构造器私有化
//2.定义一个static 静态属性对象
//3.提供一个 public 的 static 方法,可以返回一个 Dog 对象
//4.懒汉式,只有当用戶使用 getInstance 时,才返回 dog 对象,
// 后面再次调用时,返回上一次创建的 dog 对象
//5. 这样就保证了单例
private Dog(String name) {
System.out.println("构造器调用...");
this.name = name;
}
public static Dog getInstance() {
if (dog == null) {//如果还没有,就创建 dog 对象
dog = new Dog("喵喵喵");
}
return dog;
}
}
4.3 饿汉式VS懒汉式
1.二者最主要的区别在于创建对象的时机不同:
饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
2.饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
3.饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
在javaSE标准类中,java.lang.Runtime就是经 典的单例模式。
5.final关键字
5.1 基本介绍
final中文意思:最后的,最终的.
final可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰
2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【访问修饰符 final 返回类型 方法名】
3)当不希望类的的某个属性的值被修改,可以用final修饰【 public final double MAX_TEAT=0.99】
4)当不希望某个局部变量被修改,可以使用final修饰【final double MAX_TEAT=0.99】
package hsp.final_;
public class Final01 {
public static void main(String[] args) {
}
}
//要求 A 类不能被其他类继承
//可以使用 final 修饰 A 类
//final class A { }
//class B extends A {}
class C {
//如果我们要求 say 不能被子类重写
//可以使用 final 修饰 say 方法
public final void say() {}
}
class D extends C {
// @Override
//public void say() {
//System.out.println("重写了 C 类的 hi 方法..");
//}
}
//当不希望类的的某个属性的值被修改,可以用 final
class E {
public final double MAX_TEXT = 0.88;//常量
}
//当不希望某个局部变量被修改,可以使用 final 修饰
class F{
public void getAge(){
final int age =18;
//age=20;
}
}
5.2 final 使用注意事项和细节
1)final修饰的属性又叫常量,一般用XX _XX _XX来命名
2) final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择-个位置赋初值即可】:
①定义时:如public final double MAX_TEXT=0.99;
②在构造器中
③在代码块中。
3)如果final修饰的属性是静态的,则初始化的位置只能是
①定义时②在静态代码块不能在构造器中赋值。
4) final类不能继承,但是可以实例化对象。
5)如果类不是final类,但是含有final方法, 则该方法虽然不能重写,但是可
以被继承。
查看下面代码:
package hsp.final_;
public class Final02 {
public static void main(String[] args) {
}
}
class AA {
/*
1. 定义时:如 public final double TAX_RATE=0.08;
2. 在构造器中
3. 在代码块中
*/
public final double TAX_RATE = 0.01;//1.定义时赋值
public final double TAX_RATE2;
public final double TAX_RATE3;
public AA() {//构造器中赋值
TAX_RATE2 = 1.1;
}
{//在代码块赋值
TAX_RATE3 = 2.2;
}
}
class BB {
/*
如果 final 修饰的属性是静态的,则初始化的位置只能是
1 定义时 2 在静态代码块 不能在构造器中赋值。
*/
public static final double TAX_RATE = 99.9;
public static final double TAX_RATE2;
static {
TAX_RATE2 = 3.3;
}
}
//final 类不能继承,但是可以实例化对象
final class CC {
}
//如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
//即,仍然遵守继承的机制.
class DD {
public final void cal() {
System.out.println("cal()方法");
}
}
class EE extends DD {
}
6)一般来说,如果一一个类已经是final类了,就没有必要再将方法修饰成final方法。
7) final不能修饰构造方法(即构造器)
8) final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。
class Demo{
public static final int i=16; //
static{
System.out.println("codeSE教育~");
}
}
9)包装类(Integer,Double,Float, Boolean等都是final),String也是final类。
6、抽象类
基本介绍
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract 来修饰该类就是抽象类。
1)用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
}
2)用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
抽象类使用的注意事项和细节
1)抽象类不能被实例化
2)抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3)一旦类包含了abstract方法,则这个类必须声明为abstract
4) abstract 只能修饰类和方法,不能修饰属性和其它的。
package hsp.abstract_;
public class Abstract01 {
public static void main(String[] args) {
//抽象类,不能被实例化
//new A();
}
}
//抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法
//还可以有实现的方法。
abstract class A {
public void hi() {
System.out.println("hi");
}
}
//一旦类包含了 abstract 方法,则这个类必须声明为 abstract
abstract class B {
public abstract void hi();
}
//abstract 只能修饰类和方法,不能修饰属性和其它的
class C {
// public abstract int n1 = 1;
}
5)抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
6)抽象方法不能有主体,即不能实现.
7)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
8)抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
代码演示:
//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
public int n1 = 10;
public static String name = "韩顺平教育";
public void hi() {
System.out.println("hi");
}
public abstract void hello();
public static void ok() {
System.out.println("ok");
}
}
案例:
package hsp.abstract_;
public class Abstract03 {
public static void main(String[] args) {
Manager manager = new Manager("小李", 1001, 20000);
manager.work();
CommonEmployee commonEmployee = new CommonEmployee("老王", 2002, 3000);
commonEmployee.work();
}
}
abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
//将 work 做成一个抽象方法
public abstract void work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
class Manager extends Employee {
private double bonus;
public Manager(String name, int id, double salary) {
super(name, id, salary);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() { //实现父类的抽象方法
System.out.println("经理 " + getName() + " 工作中...");
}
}
class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() { //实现父类的抽象方法
System.out.println("员工 " + getName() + " 工作中...");
}
}
7、 抽象类最佳实践-模板设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
模板设计模式能解决的问题
1)当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
代码演示:
package hsp.abstract_;
// 抽象类-模板设计模式
public abstract class Template {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime(); //这里还是需要有良好的 OOP 基础,对多态
BB bb = new BB();
bb.calculateTime();
}
}
abstract class Template1 {
public abstract void job();//抽象方法
public void calculateTime() {//实现方法,调用 job 方法
//得到开始的时间
long start = System.currentTimeMillis();
job(); //动态绑定机制
//得的结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间 " + (end - start));
}
}
class AA extends Template1 {
//计算任务
//1+....+ 800000
@Override
public void job() { //实现 Template 的抽象方法 job
long num = 0;
for (long i = 1; i <= 800000; i++) {
num += i;
}
}
}
class BB extends Template1 {
public void job() {//这里也去,重写了 Template 的 job 方法
long num = 0;
for (long i = 1; i <= 80000; i++) {
num *= i;
}
}
}
8、接口
8.1 基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
语法:
interface接口名{ //属性
//抽象方法
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
★小结:
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk7.0】。接口体现了程序设计的多态和高内聚低偶合的设计思想。
特别说明:Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
package hsp.interface_;
public class Interface01 {
public static void main(String[] args) {
Iphone15 iphone15 = new Iphone15();
Mate60 mate60 = new Mate60();
iphone15.insert();
iphone15.extract();
mate60.insert();
mate60.extract();
}
}
interface USB{ //接口定义规范
public void insert();
public void extract();
}
//实现两个接口
class Iphone15 implements USB{
@Override
public void insert() {
System.out.println("iphone 15 插入充电....");
}
@Override
public void extract() {
System.out.println("iphone 15 拔出充电....");
}
}
//实现两个接口
class Mate60 implements USB{
@Override
public void insert() {
System.out.println("华为 Mate60 插入充电....");
}
@Override
public void extract() {
System.out.println("华为 Mate60 拔出充电....");
}
}
package hsp.interface_;
public class Interface02 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
test(mysqlDB);
OracleDB oracleDB = new OracleDB();
test(oracleDB);
}
public static void test(DBInterface db) {
db.connect();
db.close();
}
}
interface DBInterface { //数据库接口
public void connect();//连接方法
public void close();//关闭连接
}
class MysqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接mysql");
}
@Override
public void close() {
System.out.println("关闭mysql");
}
}
class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接oracle");
}
@Override
public void close() {
System.out.println("关闭oracle");
}
}
8.2 注意事项和细节
1)接口不能被实例化
2)接口中所有的方法是public方法, 接口中抽象方法,可以不用abstract修饰
void aaa();
实际上是abstract void aa( );
3)一个普通类实现接口,就必须将该接口的所有方法都实现。
4)抽象类实现接口,可以不用实现接口的方法。
代码演示:
package hsp.interface_;
public class Interface03 {
public static void main(String[] args) {
//new Animal(); //'Animal' 为 abstract;无法实例化
}
}
//1.接口不能被实例化
//2.接口中所有的方法是 public 方法, 接口中抽象方法,可以不用 abstract 修饰
//3.一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用 alt+enter 来解决
//4.抽象类去实现接口时,可以不实现接口的抽象方法
interface Animal {
void say();
void hi();
}
class Cat implements Animal {
@Override
public void say() {
}
@Override
public void hi() {
}
}
//抽象类可以不实现接口方法
abstract class Tiger implements Animal {
}
5)一个类同时可以实现多个接口[举例]
6)接口中的属性,只能是final的,而且是public static final 修饰符。比如:
int a=1;实际上是public static final int a= 1; (必须初始化)
7)接口中属性的访问形式:接口名.属性名
8)接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C{ }
9)接口的修饰符只能是public和默认,这点和类的修饰符是一样的。
package hsp.interface_;
public class InterFace04 {
}
interface IA {
//接口中的属性,只能是 final 的,而且是 public static final 修饰符
int n1 = 10; //等价 public static final int n1 = 10;
void hi();
}
interface IB {
void say();
}
//接口不能继承其它的类,但是可以继承多个别的接口
interface IC extends IA, IB {
}
//一个类同时可以实现多个接口
class Pig implements IB,IA {
@Override
public void hi() {
}
@Override
public void say() {
}
}
class Dog implements IC{
@Override
public void hi() {
}
@Override
public void say() {
}
}
8.3 按口和继承解决的问题不同
➢继承的价值主要在于:解决代码的复用性和可维护性。
➢接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活…
➢接口比继承更加灵活
接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系。
➢接口在一定程度上实现代码解耦【即:接口规范性+动态绑定机制】
代码演示:
package hsp.interface_;
public class ExtendsVsInterface {
public static void main(String[] args) {
Person person = new Person("小明");
person.eatting();
person.flying();
person.swimming();
}
}
class Fish{
private String name;
public Fish(String name) {
this.name = name;
}
public void eatting() {
System.out.println(name + " 会吃虾...");
}
public String getName() {
return name;
}
}
//接口
interface Fishable {
void swimming();
}
interface Birdable {
void flying();
}
//继承并实现
class Person extends Fish implements Fishable,Birdable{
public Person(String name) {
super(name);
}
@Override
public void flying() {
System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
@Override
public void swimming() {
System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
}
}
8.4 接口的多态特性
package hsp.interface_;
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 if01 可以指向 实现了 IF 接口类的对象实例
IF if01 = new Monster();
if01 = new Car();
//继承体现的多态
//父类类型的变量 a 可以指向 继承 AAA 的子类的对象实例
AAA a = new BBB();
a = new CCC();
}
}
interface IF {}
class Monster implements IF{}
class Car implements IF{}
class AAA {
}
class BBB extends AAA {}
class CCC extends AAA {}
/**
* 演示多态传递现象
*/
class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果 IG 继承了 IH 接口,而 Teacher 类实现了 IG 接口
//那么,实际上就相当于 Teacher 类也实现了 IH 接口. //这就是所谓的 接口多态传递现象. IH ih = new Teacher();
}
}
interface IH {
void hi();
}
interface IG extends IH{ }//接口继承接口
class Teacher implements IG {
@Override
public void hi() {
}
}
package hsp.interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 -> 接口类型数组
TypeC[] typeCs = new TypeC[2];
typeCs[0] = new Phone();
typeCs[1] = new Camera();
/*
给 TypeC 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(),
请遍历 TypeC 数组,如果是 Phone 对象,除了调用 TypeC 接口定义的方法外,
还需要调用 Phone 特有方法 call
*/
for (int i = 0; i < typeCs.length; i++) {
typeCs[i].work();//动态绑定.. //需要进行类型的向下转型
if (typeCs[i] instanceof Phone) {//判断他的运行类型是 Phone
((Phone) typeCs[i]).call();
}
}
}
}
interface TypeC {
void work();
}
class Phone implements TypeC {
public void call() {
System.out.println("手机可以打电话...");
}
@Override
public void work() {
System.out.println("手机工作中...");
}
}
class Camera implements TypeC {
@Override
public void work() {
System.out.println("相机工作中...");
}
}
9、内部类
定义类在局部位置(方法中/代码块) : (1) 局部内部类 (2) 匿名内部类
定义在成员位置 :(1) 成员内部类 (2) 静态内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员[思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类],内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
package hsp.innerclass;
public class Inner01 { //外部其他类
public static void main(String[] args) {
}
}
class Outer { //外部类
private int n1 = 100;//属性
public Outer(int n1) {//构造器
this.n1 = n1;
}
public void m1() {//方法
System.out.println("m1()");
}
{//代码块
System.out.println("代码块...");
}
class Inner { //内部类, 在 Outer
}
}
定义在外部类局部位置上(比如方法内):
1)局部内部类(有类名)
2)匿名内部类(没有类名,重点!!!
)
定义在外部类的成员位置上:
1)成员内部类(没用static修饰)
2)静态内部类(使用static修饰)
局部内部类的使用
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
3.作用域:仅仅在定义它的方法或代码块中。
4.局部内部类—访问---->外部类的成员[访问方式:直接访问]
5.外部类—访问---->局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内)记住:
(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
6.外部其他类—不能访问----->局部内部类(因为局部内部类地位是一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package hsp.innerclass;
/*
* 局部内部类的使用
* */
public class Inner02 {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();//99 , code SE
}
}
class Outer02{ //外部类
private int n1 = 99;
private void m2(){
System.out.println("code SE");
}
public void m1(){
//局部内部类是定义在外部类的局部位置,通常在方法中
//不能添加访问修饰符,但是可以使用final修饰
//作用域:仅仅在定义它的方法或者代码块中
final class Inner02{ //内部类(本质也是一个类)
private int n1 =100;
//可以直接访问外部类的所有成员,包含私有
public void f1(){
//如果外部类和局部内部类的成员重名时,默认遵循就近原则,
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("内部类的n1="+n1);
System.out.println("外部类的n1="+Outer02.this.n1);
m2();
}
}
//外部类在方法中,可以创建内部类的对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类
匿名内部类的使用(重要!!!)
(1)本质是类(2)内部类(3)该类没有名字(4)同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
1.匿名内部类的基本语法
new 类或接口 (参数列表) {
类体
};
package hsp.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 { //外部类
private int n1 = 10;//属性
public void method() {//方法
//基于接口的匿名内部类
//1.需求: 想使用 IA 接口,并创建对象
//2.传统方式,是写一个类,实现该接口,并创建对象
//3.需求是 Tiger/Dog 类只是使用一次,后面再不使用
//4. 可以使用匿名内部类来简化开发
//5. tiger 的编译类型 ? IA
//6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫你...");
}
}; // ; 是一个语句
System.out.println("tiger 的运行类型="+tiger.getClass());
tiger.cry();
tiger.cry();
tiger.cry();
/*
* 看底层 会分配 类名 Outer04$1
*
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
* jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址返回给 tiger
* 匿名内部类使用一次,就不能再使用
*
* */
//演示基于类的匿名内部类
//1. father 编译类型 Father
//2. father 运行类型 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了 test 方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
//5. 注意("jack") 参数列表会传递给 构造器
Person person = new Person("jack"){
@Override
public void test() {
System.out.println("匿名内部类重写了 test 方法");
}
};
System.out.println("father 对象的运行类型=" + person.getClass());//Outer04$2
person.test();
//基于抽象类的匿名内部类
new Animal(){
@Override
void eat() {
System.out.println("基于抽象类的匿名内部类");
}
}.eat();
}
}
interface IA {//接口
public void cry();
}
class Person{
public Person(String name) {
System.out.println("接收到 name=" + name);
}
public void test(){}
}
abstract class Animal { //抽象类
abstract void eat();
}
2、匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
3.可以直接访问外部类的所有成员,包含私有的
4.不能添加访问修饰符,因为它的地位就是一个局部变量。
5.作用域:仅仅在定义它的方法或代码块中。
6.匿名内部类—访问---->外部类成员[访问方式:直接访问]
7.外部其他类—不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
8.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
代码如下:
package hsp.innerclass;
public class AnonymousInnerClass02 {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
//外部其他类---不能访问----->匿名内部类
System.out.println("main outer05 hashcode=" + outer05);
/*输出
* 匿名内部类重写了 hi 方法 n1=88 外部内的 n1=99
* Outer05.this hashcode=hsp.innerclass.Outer05@a14482
* main outer05 hashcode=hsp.innerclass.Outer05@a14482
* */
}
}
class Outer05 {
private int n1 = 99;
public void f1() {
//创建一个基于类的匿名内部类
//不能添加访问修饰符,因为它的地位就是一个局部变量
//作用域 : 仅仅在定义它的方法或代码块中
People p = new People() {
private int n1 = 88;
@Override
public void hi() {
//可以直接访问外部类的所有成员,包含私有的
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 +
" 外部内的 n1=" + Outer05.this.n1);
//Outer05.this 就是调用 f1 的 对象
System.out.println("Outer05.this hashcode=" + Outer05.this);
}
};
p.hi();//动态绑定, 运行类型是 Outer05$1
//也可以直接调用, 匿名内部类本身也是返回对象
// class 匿名内部类 extends People {}
// new People(){
// @Override
// public void hi() {
// System.out.println("匿名内部类重写了 hi 方法,哈哈...");
// }
// @Override
// public void ok(String str) {
// super.ok(str);
// }
// }.ok("jack");
}
}
class People {
public void hi() {
System.out.println("People hi()");
}
public void ok(String str) {
System.out.println("People ok() " + str);
}
}
匿名内部类的最佳实践
package hsp.innerclass;
public class InnerClassExercise01 {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());
}
//静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
}
//接口
interface IL {
void show();
}
//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}
成员内部类的使用
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
1)可以直接访问外部类的所有成员,包含私有的
2) 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
3) 作用域
java和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.
4) 成员内部类—访问---->外部类成员(比如:属性)[访问方式:直接访问]
5)外部类—访问------>成员内部类(说明)访问方式:创建对象,再访问
6)外部其他类—访问---->成员内部类
7)如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
代码演示:
package hsp.innerclass;
public class AnonymousInnerClass03 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式
//老韩解读
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员
// 这就是一个语法,
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08 对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类
private int n1 = 666;
public String name = "codeSE";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成员内部类,是定义在外部内的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
public class Inner08 {//成员内部类
private double sal = 12.34;
private int n1 = 999;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
// 可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个 Inner08 实例
public Inner08 getInner08Instance() {
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1)可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2)可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
3)作用域:同其他的成员,为整个类体
4)静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有态成员]
5)外部类—访问------>静态内部类访问方式:创建对象,再访问
6)外部其他类—访问----->静态内部类
7)如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
代码:
package hsp.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他类 使用静态内部类
//方式 1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式 2
//编写一个方法,可以返回静态内部类的对象实例.
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();
/*输出
* 教育你一下 外部类 name= 小玉玉
* 教育你一下 外部类 name= 小玉玉
* ============
* 教育你一下 外部类 name= 小玉玉
* ************
* 教育你一下 外部类 name= 小玉玉
* */
}
}
class Outer10 { //外部类
private int n1 = 10;
private static String name = "小玉玉";
private static void cry() {
}
static class Inner10 {
private static String name = "教育你一下";
public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
System.out.println(name + " 外部类 name= " + Outer10.name);
cry();
}
}
public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
第6章 枚举和注解
基本介绍:
1)枚举对应英文(enumeration,简写enum)
2)枚举是一组常量的集合。
3)可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
枚举的二种实现方式
1)自定义类实现枚举
2)使用enum关键字实现枚举
自定义类实现枚举
1.不需要提供setXxx方法,因为枚举对象值通常为只读.
2.对枚举对象/属性使用final + static 共同修饰,实现底层优化.
3.枚举对象名通常使用全部大写,常量的命名规范.
4.枚举对象根据需要,也可以有多个属性
代码:
package hsp.enum_;
public class Enumeration01 {
public static void main(String[] args) {
System.out.println(Season.AUTUMN);
System.out.println(Season.SPRING);
}
}
//演示字定义枚举实现
class Season {//类
private String name;
private String desc;//描述
//定义了四个对象, 固定.
public static final Season SPRING = new Season("春天", "温暖");
public static final Season WINTER = new Season("冬天", "寒冷");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season SUMMER = new Season("夏天", "炎热");
//1. 将构造器私有化,目的防止 直接 new
//2. 去掉 setXxx 方法, 防止属性被修改
//3. 在 Season 内部,直接创建固定的对象
//4. 优化,可以加入 final 修饰符
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
小结:
进行自定义类实现枚举,有如下特点:
1)构造器私有化
2)本类内部创建一组对象[四个春夏秋冬]
3)对外暴露对象(通过为对象添加 public final static修饰符)4)可以提供get方法,但是不要提供 set
enum 关键字实现枚举
代码:
package hsp.enum_;
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season2.AUTUMN);
}
}
//使用 enum 关键字来实现枚举类
enum Season2 {//类
//使用了 enum 来实现枚举类
//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
// SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可
//4. 如果使用 enum 来实现枚举,要求将定义常量对象,写在前面
//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),
AUTUMN("秋天", "凉爽"),SUMMER("夏天", "炎热"),WHAT();
private String name;
private String desc;//描述
Season2(){//无参构造器
}
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum关键字实现枚举注意事项
l)当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类[如何证明],使用javap 工
具来演示
2)传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”);简化成 SPRING(“春天”, “温暖”),这里必须知道,它调用的是哪个构造器.
3)如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
4)当有多个枚举对象时,使用,间隔,最后有一个分号结尾
5)枚举对象必须放在枚举类的行首.
enum 常用方法说明
说明:使用关键字 enum 时,会隐式继承 Enum 类, 这样我们就可以使用 Enum 类相关的方法。[
- toString:Enum 类已经重写过了,返回的是当前对象
名,子类可以重写该方法,用于返回对象的属性信息 - name:返回当前对象名(常量名),子类中不能重写
- ordinal:返回当前对象的位置号,默认从 0 开始
- values:返回当前枚举类中所有的常量
- valueOf:将字符串转换成枚举对象,要求字符串必须
为已有的常量名,否则报异常! - compareTo:比较两个枚举常量,比较的就是编号!
package hsp.enum_;
public class Enumeration03 {
public static void main(String[] args) {
Season3 season3 = Season3.AUTUMN;
//输出枚举对象的名字
System.out.println(season3.name());//AUTUMN
//ordinal() 输出的是该枚举对象的次序/编号,从 0 开始编号
System.out.println(season3.ordinal());//2
//从反编译可以看出 values 方法,返回 Season3[]
System.out.println(season3.hashCode());//10568834
Season3[] values = season3.values();
System.out.println("===遍历取出枚举对象(增强 for)====");
for (Season3 season: values) {//增强 for 循环
System.out.println(season);
}
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行流程
//1. 根据你输入的 "AUTUMN" 到 Season2 的枚举对象去查找
//2. 如果找到了,就返回,如果没有找到,就报错
Season3 autumn1 = Season3.valueOf("AUTUMN");
System.out.println("autumn1=" + autumn1);//autumn1=Season{name='秋天', desc='凉爽'}
System.out.println(season3 == autumn1);//true
//compareTo:比较两个枚举常量,比较的就是编号
//Season3.AUTUMN - Season3.SPRING ,编号相减
System.out.println(Season3.AUTUMN.compareTo(Season3.SPRING));//2
}
}
//enum 常用方法应用实例
enum Season3 {//类
SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),
AUTUMN("秋天", "凉爽"),SUMMER("夏天", "炎热"),WHAT();
private String name;
private String desc;//描述
Season3(){//无参构造器
}
private Season3(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum 实现接口
1)使用enum关键字后,就不能再继承其它类了,因为enum 会隐式继承Enum,而Java是单继承机制。
2)枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口1,接口2
package hsp.enum_;
public class Enumeration04 {
public static void main(String[] args) {
Music.CLASSICMUSIC.playing();
}
}
class A {
}
//1.使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制
//enum Season3 extends A {
//
//}
//2.enum 实现的枚举类,仍然是一个类,所以还是可以实现接口的.
interface IPlaying {
public void playing();
}
enum Music implements IPlaying {
CLASSICMUSIC;
@Override
public void playing() {
System.out.println("播放好听的音乐...");
}
}
注解
11.12注解的理解
l)注解(Annotation)也被称为元数据(Metadata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。
2)和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
3)在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在.JavaEE中注解占据了更重要的角
色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等。
基本的 Annotation介绍
使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素
三个基本的 Annotation:
- @Override:限定某个方法,是重写父类方法,该注解只能用于方法
2)@Deprecated:用于表示某个程序元素(类,方法等)已过时 - @SuppressWarnings: 抑制编译器警告
package hsp.annotation_;
public class Override01 {
}
class Father{//父类
public void fly(){
System.out.println("Father fly...");
}
public void say(){}
}
class Son extends Father{
//1. @Override 注解放在 fly 方法上,表示子类的 fly 方法时重写了父类的 fly
//2. 这里如果没有写 @Override 还是重写了父类 fly
//3. 如果你写了@Override 注解,编译器就会去检查该方法是否真的重写了父类的
// 方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误
@Override
public void fly(){
}
}
Override使用说明
1.@Override表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
2.如果不写@Override注解,而父类仍有public void fly00,仍然构成重写3.@Override只能修饰方法,不能修饰其它类,包,属性等等
4.查看@Override注解源码为@Target(ElementType.METHOD),说明只能修饰方法
5.@Target是修饰注解的注解,称为元注解,记住这个概念.
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@Deprecated的说明
1.用于表示某个程序元素(类,方法等)已过时2.可以修饰方法,类,字段,包,参数等等
3.@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD,
PACKAGE, PARAMETER, TYPE))
4.@Deprecated的作用可以做到新旧版本的兼容和过渡
package hsp.annotation_;
public class Annotationo1 {
public static void main(String[] args) {
A a = new A();
a.hi();
}
}
//1. @Deprecated 修饰某个元素, 表示该元素已经过时
//2. 即不在推荐使用,但是仍然可以使用
//3. 查看 @Deprecated 注解类的源码
@Deprecated
class A {
@Deprecated
public int n1 = 10;
@Deprecated
public void hi(){
}
}
@SuppressWarnings: 抑制编译器警告
@SuppressWarnings 注解的案例
- unchecked是忽略没有检查的警告
- rawtypes是忽略没有指定泛型的警告(传参时没有指定泛型的警告错误)
- unused是忽略没有使用某个变量的警告错误
- @SuppressWarnings可以修饰的程序元素为,查看@Target
5)生成@SupperssWarnings 时,不用背,直接点击左侧的黄色提示,就
可以选择(注意可以指定生成的位置)
package hsp.annotation_;
import java.util.ArrayList;
import java.util.List;
//1. 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings 注解来抑制警告信息
//2. 在{""} 中,可以写入你希望抑制(不显示)警告信息
//3. 可以指定的警告类型有:
//all,抑制所有警告
//boxing,抑制与封装/拆装作业相关的警告
//cast,抑制与强制转型作业相关的警告
//dep-ann,抑制与淘汰注释相关的警告
//deprecation,抑制与淘汰的相关警告
//fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
//finally,抑制与未传回 finally 区块相关的警告
//hiding,抑制与隐藏变数的区域变数相关的警告
//incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
//javadoc,抑制与 javadoc 相关的警告
//nls,抑制与非 nls 字串文字相关的警告
//null,抑制与空值分析相关的警告
//rawtypes,抑制与使用 raw 类型相关的警告
//resource,抑制与使用 Closeable 类型的资源相关的警告
//restriction,抑制与使用不建议或禁止参照相关的警告
//serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告
//static-access,抑制与静态存取不正确相关的警告
//static-method,抑制与可能宣告为 static 的方法相关的警告
//super,抑制与置换方法相关但不含 super 呼叫的警告
//synthetic-access,抑制与内部类别的存取未最佳化相关的警告
//sync-override,抑制因为置换同步方法而遗漏同步化的警告
//unchecked,抑制与未检查的作业相关的警告
//unqualified-field-access,抑制与栏位存取不合格相关的警告
//unused,抑制与未用的程式码及停用的程式码相关的警告
//4. 关于 SuppressWarnings 作用范围是和你放置的位置相关
// 比如 @SuppressWarnings 放置在 main 方法,那么抑制警告的范围就是 main
// 通常我们可以放置具体的语句, 方法, 类.
//@SuppressWarnings({"all"})
public class Annotation02 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
int i;
System.out.println(list.get(1));
}
public void f1() {
@SuppressWarnings({"rawtypes"})
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
// @SuppressWarnings({"unused"})
int i;
System.out.println(list.get(1));
}
}
JDK5 的元 Annotation(元注解, 了解)
第7章 异常
1 基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常"。(开发过程中的语法错误和逻辑错误不是异常)
●执行过程中所发生的异常事件可分为两大类
- Error(错误): Java虚拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError[栈溢出]和OOM(out of
memory), Error 是严重错误,程序会崩溃。 - Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
package hsp.exception_;
public class Exception01 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
//int res = num1 / num2;
//System.out.println("程序继续运行....");
try {
int res = num1 / num2;
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常的原因:" + e.getMessage());//输出异常信息
}
System.out.println("程序继续运行....");
}
}
异常体系图:
小结:
1.异常分为两大类:运行时异常和编译时异常.
2.运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
3.对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
4.编译时异常,是编译器要求必须处置的异常。
2.运行时异常
2.1 常见的运行时异常包括
- NullPointerException 空指针异常
- ArithmeticException 数学运算异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 数字格式不正确异常[]
常见的运行时异常举例:
package hsp.exception_;
public class Exception02 {
public static void main(String[] args) {
//1) NullPointerException 空指针异常
String name = null;
System.out.println(name.length());
//2) ArithmeticException 数学运算异常
String desc = "描述信息";
int n = Integer.parseInt(desc);
System.out.println(n);
//3) ArrayIndexOutOfBoundsException 数组下标越界异常
//用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
int[] arr = {1,2,4};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
//4) ClassCastException 类型转换异常
//当试图将对象强制转换为不是实例的子类时,抛出该异常。
A b = new B(); //向上转型
B b2 = (B)b;//向下转型,这里是 OK
C c2 = (C)b;//这里抛出 ClassCastException
//5) NumberFormatException 数字格式不正确异常
//当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,
// 抛出该异常 => 使用异常我们可以确保输入是满足条件数字
String names = "中国";
//将 String 转成 int
int num = Integer.parseInt(names);//抛出 NumberFormatException
System.out.println(num);//1234
}
}
class A {}
class B extends A {}
class C extends A {}
3. 编译异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
3.2常见的编译异常
SQLException//操作数据库时,查询表可能发生异常IOException//操作文件时,发生的异常
FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException//加载类,而该类不存在时,异常
EOFException//操作文件,到文件未尾,发生异常
IllegalArguementException//参数异常
案例说明
package hsp.exception_;
import java.io.FileInputStream;
import java.io.IOException;
public class Exception03 {
public static void main(String[] args) {
try {
FileInputStream fis;
fis = new FileInputStream("d:\\1.jpg");
int len;
while ((len = fis.read())!=-1){
System.out.println(len);
}
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4.异常处理
基本介绍
异常处理就是当异常发生时,对异常处理的方式。
异常处理的方式
- try-catch-finally
程序员在代码中捕获发生的异常,自行处理 - throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
12.9 try-catch异常处理
12.9.1 try-catch方式处理异常说明
1)Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有多个try…catch块。
2)基本语法
try {
//可疑代码
//将异常生成对应的异常对象,传递给catch块
}
catch(异常){
//对异常的处理
}
//如果没有finally,语法是可以通过
注意细节:
1)如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块.
2)如果异常没有发生,则顺序执行try的代码块,不会进入到catch.
3)如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用- finally {}
package hsp.try_catch_;
public class TryCatch01 {
public static void main(String[] args) {
//1. 如果异常发生了,则异常发生后面的代码不会执行,直接进入到 catch 块
//2. 如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
//3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码- finally
try {
String name = "中华名族";
int n = Integer.parseInt(name);
System.out.println("转换为:"+n);
} catch (NumberFormatException e) {
System.out.println("输出的异常信息:"+e.getMessage());// 输出的异常信息:For input string: "中华名族"
}finally {
System.out.println("finally部分的执行.");
}
System.out.println("程序继续...");
}
}
4)可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch.
package hsp.try_catch_;
public class TryCatch02 {
public static void main(String[] args) {
//1.如果 try 代码块有可能有多个异常
//2.可以使用多个 catch 分别捕获不同的异常,相应处理
//3.要求子类异常写在前面,父类异常写在后面
try {
Person1 person = new Person1();
//person = null;
System.out.println(person.getName());//NullPointerException
int n1 = 10;
int n2 = 0;
int res = n1 / n2;//ArithmeticException
} catch (NullPointerException e) {
System.out.println("空指针异常:" + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("算术异常:" + e.getMessage());
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
}
}
}
class Person1 {
String name = "codeSE";
public String getName() {
return name;
}
}
5)可以进行try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑
try{
int n1 = 10;
int n2 = 0;
System.out.println(n1 / n2);
}finally {
System.out.println("执行了 finally..");
}
System.out.println("程序继续执行..");
try-catch-finally执行顺序小结:
1)如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
2)如果出现异常,则try块中异常发生后,try块剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句!
package hsp.try_catch_;
import java.util.Scanner;
public class TryCatch03 {
public static void main(String[] args) {
//如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
//思路
//1. 创建 Scanner 对象
//2. 使用无限循环,去接收一个输入
//3. 然后将该输入的值,转成一个 int
//4. 如果在转换时,抛出异常,说明输入的内容不是一个可以转成 int 的内容
//5. 如果没有抛出异常,则 break 该循环
Scanner scanner = new Scanner(System.in);
int num = 0;
String inputStr = "";
while (true) {
System.out.println("请输入一个整数:"); //
inputStr = scanner.next();
try {
num = Integer.parseInt(inputStr); //这里是可能抛出异常
break;
} catch (NumberFormatException e) {
System.out.println("你输入的不是一个整数:");
}
}
System.out.println("你输入的值是=" + num);
}
}
5.throws异常处理
5.1基本介绍
1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
package hsp.throws_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Throws01 {
public static void main(String[] args) {
}
//文件流对象
// FileNotFoundException 编译异常
//使用 throws 抛出异常,让调用f1方法的调用者处理
//throws 后面的异常类可以是方法中产生的异常类型,也可以是他的父类
//throws 后面也可以是异常列表,即可以抛出多个异常
public void f1() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("d://1.png");
}
}
注意事项和使用细节
package hsp.throws_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Throws02 {
public static void main(String[] args) {
f2();
}
public static void f2() /*throws ArithmeticException*/ {
//1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
//2.对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
int n1 = 10;
int n2 = 0;
double res = n1 / n2;
}
public static void f1() throws FileNotFoundException {
//1. 因为 f3() 方法抛出的是一个编译异常
//2. 即这时,就要 f1() 必须处理这个编译异常
//3. 在 f1() 中,要么 try-catch-finally ,或者继续 throws 这个编译异常
f3(); // 抛出异常
}
public static void f3() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
public static void f4() {
//1. 在 f4()中调用方法 f5() 是 OK
//2. 原因是 f5() 抛出的是运行异常
//3. 而 java 中,并不要求程序员显示处理,因为有默认处理机制
f5();
}
public static void f5() throws ArithmeticException {
}
}
class Father { //父类
public void method() throws RuntimeException {
}
}
class Son extends Father {//子类
//3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
// 所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
//4. 在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throws
@Override
public void method() throws ArithmeticException {
}
}
6.自定义异常
基本概念
当程序中出现了某些“错误”,但该错误信息并没有在Throw
able子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
12.11.2自定义异常的步骤
1)定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
2)如果继承Exception,属于编译异常
3)如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
package hsp.throws_;
public class Throws03 {
public static void main(String[] args) /*throws AgeException*/ {
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)) {
//通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
//自定义一个异常
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
//3. 即比较方便
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}
throw 和 throws 的区别
第8章 常用类
1.包装类
1.1包装类的分类
1)针对八种基本数据类型相应的引用类型—包装类
2)有了类的特点,就可以调用类中的方法。
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
char | Character |
bpte | Byte |
short | Short |
int | Boolean |
long | Boolean |
float | Boolean |
double | Double |
1.2 包装类和基本数据的转换
1)jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱
2)jdk5 以后(含jdk5)的自动装箱和拆箱方式
3)自动装箱底层调用的是valueOf方法,比如Integer.valueOf
package hsp.wrapperType;
public class Integer01 {
public static void main(String[] args) {
//int <--> Integer 的装箱和拆箱
//jdk5 前是手动装箱和拆箱
//手动装箱 int->Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱
//Integer -> int
int i = integer.intValue();
//jdk5 后,就可以自动装箱和自动拆箱
int n2 = 200;
//自动装箱 int->Integer
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
//自动拆箱 Integer->int
int n3 = integer2; //底层仍然使用的是 intValue()方法
}
}
package hsp.wrapperType;
//其他的数据类型转换类似,现以包装类型和 String 类型的相互转换为例
public class WrapperVSString {
public static void main(String[] args) {
//包装类(Integer)->String
Integer i = 100;//自动装箱
//方式 1
String str1 = i + "";
//方式 2
String str2 = i.toString();
//方式 3
String str3 = String.valueOf(i);
//String -> 包装类(Integer)
String str4 = "12345";
Integer i2 = Integer.parseInt(str4);//使用到自动装箱
Integer i3 = new Integer(str4);//构造器
System.out.println("ok~~");
}
}
1.2 Integer 类和 Character 类的常用方法
package hsp.wrapperType;
public class WrapperMethod {
public static void main(String[] args) {
System.out.println(Integer.MIN_VALUE); //返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
}
}
1.3 String 类
- String对象用于保存字符串,也就是一组字符序列
2)字符串常量对象是用双引号括起的字符序列。例如:“你好”、“12.97"、"boy"等
3)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节4) String类较常用构造器(其它看手册);
package hsp.string_;
public class String01 {
public static void main(String[] args) {
//1.String 对象用于保存字符串,也就是一组字符序列
//2. "codeSE" 字符串常量, 双引号括起的字符序列
//3. 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
//4. String 类有很多构造器,构造器的重载, 常用的有 :
// String s1 = new String();
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[] b)
//5. String 类实现了接口 Serializable【String 可以串行化:可以在网络传输】
// 接口 Comparable [String 对象可以比较大小]
//6. String 是 final 类,不能被其他的类继承
//7. String 有属性 private final char value[]; 用于存放字符串内容
//8. 一定要注意:value 是一个 final 类型, 不可以修改:即 value 不能指向
// 新的地址,但是单个字符内容是可以变化
String name = "codeSE";
name = "tom";
final char[] value = {'a','b','c'};
char[] v2 = {'t','o','m'};
value[0] = 'H';
//value = v2; 不可以修改 value 地址
}
}
创建String 对象的两种方式:
1)方式一:直接赋值String s = “codeSE”;
2)方式二:调用构造器 String s = new String(“codeSE”);
两种创建String 对象的区别:
方式一:直接赋值 String s = “codeSE”;
方式二:调用构造器 String s2 = new String(“codeSE”);
1.方式一:先从常量池查看是否有"codeSE"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。S最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有"codeSE",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
1) String是个final类, 代表不可变的字符序列 2)字符串是不可变的。一个字符串对象一-旦被分配,其内容是不可变的
1.4 String 类的常见方法
String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因
此java设计者还提供了StringBuilder和StringBuffer来增强String的功能,
并提高效率。
方法名 | 功能描述 |
---|---|
equals | 区分大小写,判断内容是否相等 |
equalslgnoreCase | 忽略大小写的判断内容是否相等 |
length | 获取字符的个数,字符串的长度 |
indexOf | 获取字符在字符串中第1次出现的索引索引从开始,如果找不到,返回-1 |
lastIndexOf | 获取字符在字符串中最后1次出现的索引,索引从开始,如找不到,返回-1 |
substring | 截取指定范围的子串 |
trim | 去前后空格 |
charAt | 获取某索引处的字符,注意不能使用Str[index]这种方式 |
package hsp.string_;
public class String02 {
public static void main(String[] args) {
//1. equals 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//false
// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("这个人".length());//3
// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));//0
// 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);//11
System.out.println("ter 的位置=" + s1.lastIndexOf("ter"));//4
// 6.substring 截取指定范围的子串
String name = "hello,张三";
//下面 name.substring(6) 从索引 6 开始截取后面所有的内容
System.out.println(name.substring(6));//张三
//name.substring(0,5)表示从索引 0 开始截取,截取到索引 5-1=4 位置
System.out.println(name.substring(2, 5));//llo
}
}
方法 | 描述功能 |
---|---|
toUpperCase | 转成大写 |
toLowerCase | 转成小写 |
concat | 拼接字符串 |
replace | 替换字符串中的字符 |
split | 分割字符串,对于某些分割字符,我们需要转义\\等 |
compareTo | 比较两个字符串的大小 |
toCharArray | 转换成字符数组 |
format | 格式字符串:%s字符串,%c字符,%d整型,%2f浮占型 |
package hsp.string_;
public class String03 {
public static void main(String[] args) {
// 1.toUpperCase 转换成大写
String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO
// 2.toLowerCase
System.out.println(s.toLowerCase());//hello
// 3.concat 拼接字符串
String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);//宝玉林黛玉薛宝钗 together
// 4.replace 替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
//在 s1 中,将所有的 林黛玉 替换成 薛宝钗
// 解读: s1.replace() 方法执行后,返回的结果才是替换过的.
// 注意对 s1 没有任何影响
String s11 = s1.replace("宝玉", "codeSE");
System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
System.out.println(s11);//codeSE and 林黛玉 林黛玉 林黛玉
// 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
//解读:
// 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
// 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
String[] split = poem.split(",");
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
System.out.println("==分割后内容===");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
// 6.toCharArray
s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
// 7.compareTo 比较两个字符串的大小,如果前者大,
// 则返回正数,后者大,则返回负数,如果相等,返回 0
// 解读
// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcck";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2 的值
// 8.format 格式字符串
/* 占位符有:
* %s 字符串 %c 字符 %d 整型 %.2f 浮点型
*
*/
String name = "codeSE";
int age = 20;
double score = 123.1513;
char gender = '男';
//将所有的信息都拼接在一个字符串.
String info =
"我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我! ";
System.out.println(info);
//解读
//1. %s , %d , %.2f %c 称为占位符
//2. 这些占位符由后面变量来替换
//3. %s 表示后面由 字符串来替换
//4. %d 是整数来替换
//5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
//6. %c 使用 char 类型来替换
String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
String info2 = String.format(formatStr, name, age, score, gender);
System.out.println(info2);
}
}
1.5 StringBuffer
1.5.1基本介绍
● java.lang.StringBuffer代表可变的字符序列, 可以对字符串内容进行增删。
●很多方法与String相同,但StringBuffer是可变长度的。
●StringBuffer是一个容器。
1.5.2 String VS StringBuffer
- String保存的是字符串常量,里面的值不能更改,每次String类的更新实际
上就是更改地址,效率较低//private final char value[]; - StringBuffer保存的是字符串变量,里面的值可以更改,每次
StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高
//char[] value; // 这个放在堆.
package hsp.string_;
public class StringBuffer01 {
public static void main(String[] args) {
//解读
//1. StringBuffer 的直接父类 是 AbstractStringBuilder
//2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
//3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
// 该 value 数组存放 字符串内容,引出存放在堆中的
//4. StringBuffer 是一个 final 类,不能被继承
//5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
// 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
StringBuffer s1 = new StringBuffer("codeSE");
}
}
String 和 StringBuffer 相互转换
package hsp.string_;
public class StringAndStringBuffer {
public static void main(String[] args) {
//看 String——>StringBuffe
String str = "hello code";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("中国人");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}
1.6 StringBuffer 类常见方法
package hsp.string_;
public class StringBuffer02 {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
System.out.println(s);//"hello,张三丰赵敏100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏true10.5"
//改
//使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6
//插
//在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
}
}
1.7 StringBuilder 类
1.7.1基本介绍
1)一个可变的字符序列。此类提供个与StringBuffer兼容的API, 但不保证同
步(StringBuilder不是线程安全)。该类被设计用作StringBuffer的一个简易
替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。
2)在StringBuilder上的主要操作是append和insert方法,可重载这些方法,
以接受任意类型的数据。
package hsp.string_;
public class StringBuilder01 {
public static void main(String[] args) {
//解读:
//1. StringBuffer 的直接父类 是 AbstractStringBuilder
//2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
//3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
// 该 value 数组存放 字符串内容,引出存放在堆中
//4. StringBuffer 是一个 final 类,不能被继承
//5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
// 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
StringBuffer stringBuffer1 = new StringBuffer("hello");
//1. StringBuilder 继承 AbstractStringBuilder 类
//2. 实现了 Serializable ,说明 StringBuilder 对象是可以串行化(对象可以网络传输,可以保存到文件)
//3. StringBuilder 是 final 类, 不能被继承
//4. StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder 的 char[] value;
// 因此,字符序列是堆中
//5. StringBuilder 的方法,没有做互斥的处理,即没有 synchronized 关键字,因此在单线程的情况下使用
// StringBuilder
StringBuilder stringBuilder2 = new StringBuilder();
}
}
1.8 StringBuilder 常用方法
StringBuilder和StringBuffer均代表可变的字符序列,方法是一样的,所
以使用和StringBuffer一样,不重复演示.
1.9 String、 StringBuffer 和StringBuilder的比较
- StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法
也一样 - String:不可变字符序列,效率低,但是复用率高。
- StringBuffer: 可变字符序列、效率较高(增删)、线程安全,看源码
- StringBuilder: 可变字符序列、效率最高、线程不安全
- String使用注意说明:
string s= “a”; //创建了一个字符串
s += “b”; //实际上原来的" a"字符串对象已经丢弃了,现在又产生了一个字符串s+ “b” (也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
=>结论:如果我们对String做大量修改,不要使用String
package hsp.string_;
public class StringVsStringBufferVsStringBuilder {
public static void main(String[] args) {
long startTime = 0L;
long endTime = 0L;
StringBuffer buffer = new StringBuffer("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuffer 拼接 20000 次
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer 的执行时间:" + (endTime - startTime));//20ms
StringBuilder builder = new StringBuilder("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuilder 拼接 20000
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder 的执行时间:" + (endTime - startTime));//10ms
String text = "";
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//String 拼接 20000
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String 的执行时间:" + (endTime - startTime));//4585ms
}
}
String、 StringBuffer 和StringBuilder的选择
使用的原则,结论:
1.如果字符串存在大量的修改操作,-般使用StringBuffer或StringBuilder
2.如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
3.如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
4.如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder 的方法使用和StringBuffer一 样.
2、Math类
2.1 基本介绍
Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。
package math_;
public class MathMethod01 {
public static void main(String[] args) {
//看看 Math 常用的方法(静态方法)
//1.abs 绝对值
int abs = Math.abs(-9);
System.out.println(abs);//9
//2.pow 求幂
double pow = Math.pow(2, 4);//2 的 4 次方
System.out.println(pow);//16
//3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成 double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0
//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
// (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
// (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
//max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);//min=1
System.out.println("max=" + max);//max=90
}
}
3、Arrays
Arrays里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)。
- toString返回数组的字符串形式
Arrays.toString(arr) - sort排序(自然排序和定制排序) Integer arr[] = {1, -1, 7, 0, 89};
- binarySearch通过二分搜索法进行查找,要求必须排好序
int index = Arrays.binarySearch(arr, 3); - copyOf数组元素的复制
Integer[] newArr = Arrays.copyOf(arr, arr.length); - fill数组元素的填充
Integer[] num = new Integer[]{9,3,2};
Arrays.fill(num, 99); - equals比较两个数组元素内容是否完全-致
boolean equals = Arrays.equals(arr, arr2); - asList将一组值,转换成list
List< Integer> asList = Arrays.asList(2,3,4,5,6,1);
System.out.println(“asList=” + asList);
package hsp.arrays_;
import java.util.Arrays;
import java.util.Comparator;
public class ArraysMethod01 {
public static void main(String[] args) {
Integer[] integers = {1, 20, 90};
//遍历数组
// for(int i = 0; i < integers.length; i++) {
// System.out.println(integers[i]);
// }
//直接使用 Arrays.toString 方法,显示数组
System.out.println(Arrays.toString(integers));//[1, 20, 90]
//演示 sort 方法的使用
Integer arr[] = {1, -1, 7, 0, 89};
//进行排序
//老韩解读
//1. 可以直接使用冒泡排序 , 也可以直接使用 Arrays 提供的 sort 方法排序
//2. 因为数组是引用类型,所以通过 sort 排序后,会直接影响到 实参 arr
//3. sort 重载的,也可以通过传入一个接口 Comparator 实现定制排序
//4. 调用 定制排序 时,传入两个参数 (1) 排序的数组 arr
// (2) 实现了 Comparator 接口的匿名内部类 , 要求实现 compare 方法
//5. 先演示效果,再解释
//6. 这里体现了接口编程的方式 , 看看
// 源码分析
//(1) Arrays.sort(arr, new Comparator()
//(2) 最终到 TimSort 类的 private static <T> void binarySort(T[] a, int lo, int hi, int start, // Comparator<? super T> c)()
//(3) 执行到 binarySort 方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
// 匿名内部类的 compare ()
// while (left < right) {
// int mid = (left + right) >>> 1;
// if (c.compare(pivot, a[mid]) < 0)
// right = mid;
// else
// left = mid + 1;
// }
//(4) new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Integer i1 = (Integer) o1;
// Integer i2 = (Integer) o2;
// return i2 - i1;
// }
// }
//(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
// 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
// 将来的底层框架和源码的使用方式,会非常常见
//Arrays.sort(arr); // 默认排序方法
//定制排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
});
System.out.println("===排序后===");
System.out.println(Arrays.toString(arr));//[89, 7, 1, 0, -1]
}
}
package hsp.arrays_;
import java.util.Arrays;
import java.util.Comparator;
public class ArrayMethod02 {
public static void main(String[] args) {
int[] arr = {1, -1, 8, 0, 20};
//bubble01(arr);
bubble02(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int i1 = (Integer) o1;
int i2 = (Integer) o2;
return i2 - i1;// return i2 - i1;
}
});
System.out.println("==定制排序后的情况==");
System.out.println(Arrays.toString(arr));//[20, 8, 1, 0, -1]
}
//使用冒泡完成排序
public static void bubble01(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
//从小到大
if (arr[j] > arr[j + 1])
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
//结合冒泡 + 定制
public static void bubble02(int[] arr, Comparator c) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
//数组排序由 c.compare(arr[j], arr[j + 1])返回的值决定
if (c.compare(arr[j], arr[j + 1]) > 0) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
package hsp.arrays_;
import java.util.Arrays;
import java.util.List;
public class ArrayMethod003 {
public static void main(String[] args) {
Integer[] arr = {1, 2, 90, 123, 567};
// binarySearch 通过二分搜索法进行查找,要求必须排好
//1. 使用 binarySearch 二叉查找
//2. 要求该数组是有序的. 如果该数组是无序的,不能使用 binarySearch
//3. 如果数组中不存在该元素,就返回 return -(low + 1); // key not found.
int index = Arrays.binarySearch(arr, 567);
System.out.println("index=" + index);//index=4
//copyOf 数组元素的复制
//1. 从 arr 数组中,拷贝 arr.length 个元素到 newArr 数组中
//2. 如果拷贝的长度 > arr.length 就在新数组的后面 增加 null
//3. 如果拷贝长度 < 0 就抛出异常 NegativeArraySizeException
//4. 该方法的底层使用的是 System.arraycopy()
Integer[] newArr = Arrays.copyOf(arr, arr.length);
System.out.println("==拷贝执行完毕后==");
System.out.println(Arrays.toString(newArr));//[1, 2, 90, 123, 567]
//ill 数组元素的填充
Integer[] num = new Integer[]{9, 3, 2};
//1. 使用 99 去填充 num 数组,可以理解成是替换原理的元素
Arrays.fill(num, 99);
System.out.println("==num 数组填充后==");
System.out.println(Arrays.toString(num));//[99, 99, 99]
//equals 比较两个数组元素内容是否完全一致
Integer[] arr2 = {1, 2, 90, 123};
//解读
//1. 如果 arr 和 arr2 数组的元素一样,则方法 true;
//2. 如果不是完全一样,就返回 false
boolean equals = Arrays.equals(arr, arr2);
System.out.println("equals=" + equals);//equals=false
//asList 将一组值,转换成 list
//老韩解读
//1. asList 方法,会将 (2,3,4,5,6,1)数据转成一个 List 集合
//2. 返回的 asList 编译类型 List(接口)
//3. asList 运行类型 java.util.Arrays#ArrayList, 是 Arrays 类的
// 静态内部类 private static class ArrayList<E> extends AbstractList<E>
// implements RandomAccess, java.io.Serializable
List asList = Arrays.asList(2, 3, 4, 5, 6, 1);
System.out.println("asList=" + asList);//asList=[2, 3, 4, 5, 6, 1]
System.out.println("asList 的运行类型" + asList.getClass());//asList 的运行类型class java.util.Arrays$ArrayList
}
}
4、 System 类
System 类常见方法和案例
- exit退出当前程序
- arraycopy :复制数组元素,比较适合底层调用,一般使用
Arrays.copyOf完成复制数组.
int[] src={1,2,3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, 3); - currentTimeMillens:返回当前时间距离1970-1-1的毫秒数
- gc:运行垃圾回收机制System.gc);
5、日期类
5.1 第一代日期类
- Date: 精确到毫秒,代表特定的瞬间
- SimpleDateFormat: 格式和解析日期的类
SimpleDateFormat格式化和解析日期的具
体类。它允许进行格式化(日期->文本)、
解析(文本->日期)和规范化.
package date_;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Date01 {
public static void main(String[] args) throws ParseException {
//1. 获取当前系统时间
//2. 这里的 Date 类是在 java.util 包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//1. 创建 SimpleDateFormat 对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);
//1. 可以把一个格式化的 String 转成对应的 Date
//2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
String s = "1996 年 01 月 01 日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));
}
}
5.2第二代日期类
1)第二代日期类,主要就是Calendar类(日历)。
public abstract class Calendar extends Obiect implements Serializable,
Cloneable, Comparable < Calendar >
2) Calendar类是一个抽象类, 它为特定瞬间与-组诸如YEAR、MONTH、
DAY OF MONTH、HOUR 等日历字段之间的转换提供了一些方法,并为操
作日历字段(例如获得下星期的日期)提供了一些方法。
package date_;
import java.util.Calendar;
public class Date02 {
public static void main(String[] args) {
//1. Calendar 是一个抽象类, 并且构造器是 private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序
//4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) + " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND));
}
}
5.3 第三代日期类
前面两代日期类的不足分析
JDK 1.0中包含了一-个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar也存在问题是:
1)可变性:像日期和时间这样的类应该是不可变的。
2)偏移性: Date中的年份是从1900开始的,而月份都从0开始。
3)格式化:格式化只对Date有用,Calendar则不行。
4)此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)
LocalDate(日期/年月日)、 LocalTime(时间/时分秒)、 LocalDateTime(日期时
间/年月日时分秒) JDK8加入
LocalDate只包含日期,可以获取日期字段
LocalTime只包含时间,可以获取时间字段
LocalDateTime包含日期+时间,可以获取日期和时间字段
package date_;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
//第三代日期
public class LocalDate01 {
public static void main(String[] args) {
//1. 使用 now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);//2023-04-07T08:55:50.828
//2. 使用 DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter 对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//提供 plus 和 minus 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
}
DateTimeFormatter 格式日期类
类似于SimpleDateFormat
Date TimeFormat dtf = DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
package hsp.date_;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatter01 {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.now();
//关于DateTimeFormatter的各个格式参数,需要看jdk8的文档
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
String strDate = dtf.format(ldt);
System.out.println(strDate);
}
}
Instant 时间戳
类似于Date
提供了一系列和Date类转换的方式
Instant-- > Date:
Date date = Date.from(instant);
Date- > Instant:
Instant instant = date.tolnstant(O;
package hsp.date_;
import java.time.Instant;
import java.util.Date;
public class Instant01 {
public static void main(String[] args) {
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant 转成 Date
Date date = Date.from(now);
//3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象
Instant instant = date.toInstant();
System.out.println(instant);
}
}
第9章 集合
1、 集合的好处
前面我们保存多个数据使用的是数组,那么数组有不足的地方。
1.1 数组
1)长度开始时必须指定,而且一旦指定, 不能更改
2)保存的必须为同一类型的元素
3)使用数组进行增加/删除元素的示意代码-比较麻烦
写出Person数组扩容示意代码。
Person[] pers = new Person[1]; //大小是1
per[0]=new Person();
//增加新的Person对象?
Person[] pers2 = new Person[pers.length+ 1]; //新创建数组
for0{} //拷贝pers数组的元素到pers2
pers2[pers2.length-1]=new Person0://添加新的对象
1.2 集合
1)可以动态保存任意多个对象,使用比较方便!
2)提供了一系列方便的操作对象的方法: add、remove、set、 get等
3)使用集合添加,删除新元素的示意代码简洁了
1.3集合框架体系图
package hsp.collection_;
import java.util.ArrayList;
import java.util.HashMap;
public class Collection01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. 集合主要是两组(单列集合 , 双列集合)
//2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
//3. Map 接口的实现子类 是双列集合,存放的 K-V
//Collection
//Map
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
hashMap.put("NO1", "北京");
hashMap.put("NO2", "上海");
System.out.println(arrayList);//[jack, tom]
System.out.println(hashMap);//{NO2=上海, NO1=北京}
}
}
3、 Collection接口和常用方法
3.1 Collection 接口实现类的特点
public interface Collection< E> extends Iterable
- collection实现子类可以存放多个元素,每个元素可以是0bject
- 有些Collection的实现类, 可以存放重复的元素,有些不可以
- 有些Collection的实现类, 有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类, 是通过它的子接口Set和List 来
实现的
Collection接口常用方法,以实现子类ArrayList来演示.
package hsp.collection_;
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
//add 添加单个元素
list.add("red");
list.add("red");
list.add("yellow");
list.add(200);
list.add(false);
System.out.println(list);
//remove 删除指定元素
list.remove(false);//通过值删除
list.remove(0);//通过索引删除
System.out.println(list);
//contains 查找元素是否存在
System.out.println(list.contains(200));
//size:获取元素个数
System.out.println(list.size());
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
//list.clear();
//System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" +list);
}
}
3.2 Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator(方法, 用以返回
一个实现了Iterator接口的对象, 即可以返回一个迭代器。 - Iterator的结构.[看图]
- lterator仅用3遍历焦合,lterator本身并不存放对象
3.3 Collection 接口遍历对象方式2-for循环增强
增强for循环,可以代替iterator迭代器, 特点:增强for就是简化版的iterator ,
本质样。只能用于遍历集合或数组。
基本语法
for(元素类型元素名:集合名或数组名) {
访问元素
案例演示(看老师演示遍历Book,并使用Debug源码来证明)
CollectionForjava
for (Object object : col) {
System. outprintln(object);
package hsp.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator01 {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 12.3));
System.out.println("col=" + col);
//1. 先得到 col 对应的 迭代器
Iterator iterator = col.iterator();
//2. 使用 while 循环遍历
// while (iterator.hasNext()) {//判断是否还有数据
// //返回下一个元素,类型是 Object
// Object obj = iterator.next();
// System.out.println("obj=" + obj);
// }
System.out.println("===第一次遍历===");
//一个快捷键,快速生成 while => itit
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
// iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//增强for循环
System.out.println("===使用增强for遍历===");
for (Object obj : col) {
System.out.println("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
4、List 接口和常用方法
4.1 List 接口基本介绍
List接口是Collection接口的子接口List
- List集合类中元素有序(即添加顺序和取出顺序致)、 且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK API中List接口的实现类,常用的有: ArrayList、 LinkedList和Vector.
List list = new ArrayList();
package hsp.collection_list;
import java.util.ArrayList;
import java.util.List;
public class List01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
List list1 = new ArrayList();
list1.add("codeSE");
list1.add("red");
list1.add("blue");
list1.add("blue");
list1.add("tom");
System.out.println("list1=" + list1);
//2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从 0 开始的
System.out.println(list1.get(3));//blue
}
}
4.2 List 接口的常用方法
package hsp.collection_list;
import java.util.ArrayList;
import java.util.List;
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("李白");
// void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "codeSE");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list2 = new ArrayList();
list2.add("萧何");
list2.add("韩信");
list2.add("韩信");
list2.add("韩信");
list.add("codeSE");
list.addAll(2, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
System.out.println("取指定 index 位置:" + list.get(2));//萧何
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("韩信"));//3
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("codeSE"));//7
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 【fromIndex ,subList)
List returnlist = list.subList(0, 3);
System.out.println("returnlist=" + returnlist);
}
}
List 的三种遍历方式: 【ArrayList, LinkedList,Vector】通用
1)方式:使用iterator
Iterator iter = coliterator0:
while(iter.hasNext0){
Object。= iter.next);
2)方式二:使用增强for
for(Object o:col){
}
3)方式三:使用普通for
forint i=0; <list.size0;i++){
Object object = list.get(i);
System.out.println(object);
}
说明:使用LinkedList完成 使用方式和ArrayList一样
package hsp.collection_list;
import java.util.*;
public class ListFor {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// List list = new ArrayList();
// List list = new Vector();
List list = new LinkedList();
list.add("张三丰");
list.add("李白");
list.add("萧何");
list.add("CodeSE");
list.add("韩信");
list.add("韩信");
list.add("codeSE");
//1. 迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next + "\t");
}
//2. 增强 for
for (Object o : list) {
System.out.println(o + "\t");
}
//3. 使用普通 for
for (int i = 0; i < list.size(); i++) {
System.out.println("对象=" + list.get(i));
}
}
}
5、ArrayList 底层结构和源码分析
5.1 ArrayList 的注意事项
- permits all elements, including null,ArrayList可以加入null,并且多个
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.在多线程情况下,不建议使用ArrayList
5.2 ArrayList 的底层操作机制源码分析(重点,难点.)
- ArrayList中维护了-个0bject类型的数组elementData. [debug看源码]
transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列号
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0, 第1次添加, 则扩容elementData为10, 如需要再次扩容,则扩容elementData为1.5倍。
3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,
则直接扩容elementData为1.5倍。
建议:自己去debug -把我们的ArrayList的创建和扩容的流程.
待续...
8 ArrayList 和LinkedList 比较
8.1 ArrayList 和LinkedList的比较
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低、数组扩容 | 较高 |
LinkedList | 双向链表 | 较较高,通过链表追加 | 较低 |
| 如何选择ArrayList和inkedList:
1)如果我们改查的操作多, 选择ArrayList
2)如果我们增删的操作多,选择LinkedList
3)般来说, 在程序中,80%-90%都是查询, 因此大部分情况下会选择ArrayList
4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外-个模块是LinkedList,也就是说,要根据业务来进行选择
9、Set接口和常用方法
9.1 Set 接口基本介绍
1)无序(添加和取出的顺序不一致) ,没有索引后面演示]
2)不允许重复元素,所以最多包含一个null
3) JDK API中Set接口的实现类有:
9.2 Set接口的常用方法
和List接口一样, Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.
9.3 Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取.
package hsp.set_;
import java.util.HashSet;
import java.util.Iterator;
public class SetMethods01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//Set 的接口实现类 HashSet类
//1.以Set 接口的实现类HashSet来讲解Set接口的方法
//2.set接口的实现类的对象(Set接口对象),不能存放重复的元素,可以添加一个nulll
// 3.set接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
//4.注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.
HashSet set = new HashSet();
set.add("code SE");
set.add("中国");
set.add(true);
set.add(null);
set.add("code SE");//再次添加
System.out.println(set);
//遍历
//方式1:
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//方式2:增强for
for (Object s:set) {
System.out.println(s);
}
}
}
10、Set 接口实现类-HashSet
10.1 HashSet 的全面说明
- HashSet实现了Set接口
- HashSet实际上是HashMap,看下源码.
public HashSet() {
map = new HashMap<>(;
} - 可以存放null值,但是只能有一个null
- HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放元素的顺序和取出顺序一致)
- 不能有重复元素/对象.在前面Set接口使用已经讲过
package hsp.set_;
import java.util.HashSet;
public class Set02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet set = new HashSet();
//说明
//1. 在执行 add 方法后,会返回一个 boolean 值
//2. 如果添加成功,返回 true, 否则返回 false
//3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john"));//T
System.out.println(set.add("lucy"));//T
System.out.println(set.add("john"));//F
System.out.println(set.add("jack"));//T
System.out.println(set.add("Rose"));//T
set.remove("john");
System.out.println("set=" + set);//set=[Rose, lucy, jack]
set = new HashSet();
System.out.println("set=" + set);//set=[]
//4 Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Cat("tom"));//OK
set.add(new Cat("tom"));//Ok
System.out.println("set=" + set);//set=[Cat{name='tom'}, lucy, Cat{name='tom'}]
//在加深一下. 非常经典的面试题
set.add(new String("codeSE"));//OK
set.add(new String("codeSE"));//加入不了
System.out.println("set=" + set);//set=[Cat{name='tom'}, codeSE, lucy, Cat{name='tom'}]
}
}
class Cat {
String name;
public Cat(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
10.3 HashSet 底层机制说明
待续...
11、Set 接口实现类-LinkedHashSet
11.1 LinkedHashSet 的全面说明
- LinkedHashSet是 HashSet的子类
- LinkedHashSet底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
- LinkedHashSet 不允许添重复元素
12、Map 接口和常用方法
12.1 Map 接口实现类的特点 [很实用]
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map 中的key和 value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3)Map 中的key 不允许重复,原因和HashSet一样,前面分析过源码.4) Map中的value可以重复 - Map 的key可以为null, value也可以为null,注意key为null,只能有一个,value为null ,可以多个.
6)常用String类作为Map的key - key 和 value之间存在单向一对一关系,即通过指定的key总能找到对应的value
package hsp.map_;
import java.util.HashMap;
public class Map01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// Map 接口实现类的特点, 使用实现类 HashMap
//1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
//3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码
// 4. Map 中的 value 可以重复
//5. Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null,
HashMap map = new HashMap();
map.put("n1", "张三丰");
map.put("n1", "张三丰");//相同的k,等于替换
map.put("n2", "张翠山");
map.put("n3", "张无忌");
map.put("n4", "张无忌");
map.put(null,null);
map.put(null,"codeSE");//相同的k,等于替换
map.put("n5", null);
map.put("n5", null);
map.put(1,"周芷若");
map.put(new Object(),"赵敏");
System.out.println("map="+map);//map={null=codeSE, 1=周芷若, n1=张三丰, java.lang.Object@a14482=赵敏, n2=张翠山, n3=张无忌, n4=张无忌, n5=null}
// 通过 get 方法,传入 key ,会返回对应的 value
System.out.println(map.get(null));//codeSE
}
}
12.2 Map 接口常用方法
package codeSE.map_;
import java.util.HashMap;
import java.util.Map;
public class MapMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//演示 map 接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("codeSE", "codeSE 的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("值:" + val);
// size:获取元素个数
System.out.println("元素个数:" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("codeSE"));
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
12.3 Map 接口遍历方法
1)containsKey:查找键是否存在
2) keySet:获取所有的键
3)entrySet:获取所有关系k-v
values:获取所有的值
package hsp.map_;
import java.util.*;
public class MapFor01 {
@SuppressWarnings({"All"})
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("n1", "张三丰");
map.put("n1", "张三丰");//相同的k,等于替换
map.put("n2", "张翠山");
map.put("n3", "张无忌");
map.put("n4", "张无忌");
map.put(null,null);
map.put(null,"codeSE");//相同的k,等于替换
map.put("n5", null);
map.put("n5", null);
map.put(1,"周芷若");
map.put(new Object(),"赵敏");
System.out.println("源数据="+map);
//第一种: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
13、Map接口实现类-HashMap
13.1 HashMap小结
- Map接口的常用实现类:HashMap、Hashtable和Properties。
- HashMap是 Map 接口使用频率最高的实现类。
3)HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)[案例 Entry ] - key不能重复,但是值可以重复,允许使用null键和null值。
5)如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的. (jdk8的
hashMap底层数组+链表+红黑树) - HashMap没有实现同步,因此是线程不安全的方法没有做同步互斥的操作,没有synchronized
13.2 HashMap底层机制及源码剖析
待续。。。。
14、Map 接口实现类-Hashtable
14.1 HashTable 的基本介绍
1)存放的元素是键值对:即K-V
2) hashtable的键和值都不能为null, 否则会抛出NullPointerException
3) hashTable使用方法基本上和HashMap-样
4) hashTable是线程安全的(synchronized), hashMap是线程不安全的
5)简单看下底层结构
package hsp.map_;
import java.util.Hashtable;
import java.util.Map;
public class HashTable01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Map hashtable = new Hashtable();
hashtable.put("name","codeSE");
hashtable.put("age",20);
hashtable.put("age",18);//替换,可以
hashtable.put("grender","男");
hashtable.put(null,"codeSE");//异常
hashtable.put("salary",null);//异常
hashtable.put(null,null);//异常
System.out.println("hashtable="+hashtable);
}
}
14.2 Hashtable 和 HashMap 对比
版本 | 线程安全(同步) | 效率 | 允许null键,值null | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
HashTable | 1.0 | 安全 | 较低 | 不可以 |
15、Map接口实现类-Properties
15.1基本介绍
1.Properties类继承自Hashtable类井且实现了Map接口,也是使用一种键值对的形式来保存数据。
2.他的使用特点和Hashtable类似
3. Properties 还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
说明:工作后xxx.properties 文件通常作为配置文件,这个知识点在I0流举例,有兴趣可先看文章htst://ww.cblos.cm/xudone-bhunt/o/3758136.html
15.2基本使用
package hsp.map_;
import java.util.Properties;
public class Properties01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
Properties properties = new Properties();
//增加
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}
16、总结-开发中如何选择集合实现类(记住)
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1)先判断存储的类型(一组对象【单列】或一组键值对【双列】)
2) 一组对象【单列】: Collection接口
(1)允许重复: List
增删多: LinkedList [底层维护了一个双向链表]
改查多: ArrayList [底层维护Object类型的可变数组]
(2)不允许重复: Set
无序: HashSet [底层是HashMap,维护了一一个哈希表即(数组+链表+红黑树)]
排序: TreeSet [老韩举例说明]
插入和取出顺序一致: LinkedHashSet , 维护数组+双向链表
3) 一组键值对【双列】: Map
键无序: HashMap [底层是哈希表jdk7: 数组+链表,jdk8:数组+链表+红黑树]
键排序: TreeMap
键插入和取出顺序一致: LinkedHashMap
读取文件Properties
第10章 泛型
1 泛型的引出
1.1 看一个需求
package hsp.genericity_;
import java.util.ArrayList;
public class Animal {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("阿黄", "黄色", 4));
list.add(new Dog("阿狗", "灰色色", 10));
list.add(new Dog("阿花", "红色", 4));
//假如我们不小心,添加了一只猫
list.add(new Cat("阿猫", "白色", 1));
//遍历
for (Object o : list) {
//向下转型 Object ->Dog
Dog dog = (Dog) o;
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
class Dog {
private String name;
private String color;
private int age;
Dog(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
}
class Cat {
private String name;
private String color;
private int age;
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
}
1.2使用传统方法的问题分析
1)不能对加入到集合ArrayList中的数据类型进行约束(不安全)
2)遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
1.3泛型快速体验_用泛型来解决前面的问题
package hsp.genericity_;
import java.util.ArrayList;
public class Animal {
public static void main(String[] args) {
//使用传统的方法来解决===> 使用泛型
//解读
//1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型
//2. 如果编译器发现添加的类型,不满足要求,就会报错
//3. 在遍历的时候,可以直接取出 Dog 类型而不是 Object
//4. public class ArrayList<E> {} E 称为泛型,那么 Dog->E
ArrayList<Dog> list = new ArrayList<Dog>();
list.add(new Dog("阿黄", "黄色", 4));
list.add(new Dog("阿狗", "灰色色", 10));
list.add(new Dog("阿花", "红色", 4));
//list.add(new Cat("阿猫", "白色", 1)); //这时被约束后,Cat就添加不了
//遍历
for (Dog dog : list) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
class Dog {
private String name;
private String color;
private int age;
Dog(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
}
class Cat {
private String name;
private String color;
private int age;
public Cat(String name, String color, int age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public int getAge() {
return age;
}
}
2、泛型的理解和好处
2.1泛型的好处
1)编译时, 检查添加元素的类型, 提高了安全性
2)减少了类型转换的次数,提高效率[说明]
不使用泛型:
Dog -加入-> Object -取出-> Dog //放入到ArrayList会先转成Object,在取出时,还需要转换成Dog
使用泛型:
Dog-> Dog -> Dog //放入时,和取出时,不需要类型转换,提高效率
3)不再提示编译警告
3 、泛型介绍
3.1
int a =10;
理解:泛(广泛)型(类型) => Integer, String,Dog
1)泛型又称参数化类型, 是Jdk5.0出现的新特性,解决数据类型的安全性问题
2) 在类声明或实例化时只要指定好需要的具体的类型即可。
3) Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException异常。同时,代码更加简洁、健壮
4)泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。
package hsp.genericity_;
public class Genericity01 {
public static void main(String[] args) {
//注意,特别强调: E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类
Person<String> person = new Person<String>("中国人");
person.show(); //class java.lang.String
Person<Integer> person1 = new Person<>(100);
person1.show();//class java.lang.Integer
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
E s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
public Person(E s) {//E 也可以是参数类型
this.s = s;
}
public E f() {//返回类型使用 E
return s;
}
public void show() {
System.out.println(s.getClass());//显示 s
}
}
4泛型的语法
4.1泛型的声明 .
interface接口<T>{}和class类<K,V>{}
//比如: List,ArrayList
说明:
1)其中,T,K,V不代表值,而是表示类型。
2)任意字母都可以。常用T表示,是Type的缩写
4.2泛型的实例化
要在类名后面指定类型参数的值(类型)。如:
1) List<String> strList = new ArrayList <String>0;
2) Iterator<Customer> iterator = customers.iterator();
package hsp.genericity_;
import java.util.*;
public class Genericity02 {
public static void main(String[] args) {
HashSet<Student> studentHashSet = new HashSet<Student>();
studentHashSet.add(new Student("小花", 20));
studentHashSet.add(new Student("小华", 30));
studentHashSet.add(new Student("小白", 50));
for (Student student : studentHashSet) {
System.out.println(student);
}
HashMap<String, Student> studentHashMap = new HashMap<String, Student>();
studentHashMap.put("1001", new Student("小明明", 20));
studentHashMap.put("1002", new Student("小华华", 45));
studentHashMap.put("1003", new Student("小花花", 50));
//迭代器 EntrySet
Set<Map.Entry<String, Student>> entries = studentHashMap.entrySet();
Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String,Student> next = iterator.next();
System.out.println(next.getKey()+"----"+next.getValue());
}
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.4 泛型使用的注意事项和细节
1.interface List <T> {} ,public class HashSet<E>{}..等等
说明: T, E只能是引用类型
看看下面语句是否正确?:
List < Integer> list = new ArrayList< Integer> 0; //OK
List<int> list2 = new ArrayList <int> 0://错误
2.在给泛型指定具体类型后,可以传入该类型或者其子类类型
3.泛型使用形式
List<Integer> list1 = new ArrayList <Integer> 0;
List<Integer> list2 = new ArrayList< > 0;
3.如果我们这样写List list3 = new ArrayList(); 默认给它的泛型是<E> ,E就是Object
package hsp.genericity_;
import java.util.ArrayList;
import java.util.List;
public class GenericityDetail {
public static void main(String[] args) {
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误
//2. 说明
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig1 = new Pig<A>(new A());
aPig1.fn();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.fn();
//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
//在实际开发中,我们往往简写
//编译器会进行类型推断, 推荐使用下面写法
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();
//4. 如果是这样写 泛型默认是 Object
ArrayList arrayList = new ArrayList();//等价 ArrayList<Object> arrayList = new ArrayList<Object>()
/*Tiger tiger = new Tiger();
class Tiger {//类
Object e;
public Tiger() {}
public Tiger(Object e) {
this.e = e;
}
}
*/
}
}
class A{
}
class B extends A{}
class Tiger<T>{
T t;
public Tiger(T t) {
this.t = t;
}
}
class Pig<T>{
T t;
public Pig(T t) {
this.t = t;
}
public void fn(){
System.out.println(t.getClass());//运行类型
}
}
5 自定义泛型
5.1自定义泛型类(难度)
➢基本语法
class类名<T, R… >{ //…表示可以有多个泛型
成员
}
注意细节
1)普通成员可以使用泛型(属性、方法)
2)使用泛型的数组,不能初始化
3)静态方法中不能使用类的泛型
4)泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
5)如果在创建对象时,没有指定类型,默认为0bject
package hsp.genericity_;
import java.util.Arrays;
/*
* 自定义泛型
* */
public class CustomGenericity01 {
public static void main(String[] args) {
Monkey<String, Integer, Boolean> monkey = new Monkey<>("猴子",100, false );
Monkey<Double,Integer,String> monkey1 = new Monkey<>(12.2,25,"code");
Monkey<String, Integer, Boolean> monkey2 = new Monkey<>("候类","猴子",100, false);
//T=Double, R=String, M=Integer
Monkey<Double,String,Integer> g = new Monkey<>("大猴子");
g.setR(10.9); //OK
//g.setR("yy"); //错误,类型不对
System.out.println(g);
Monkey g2 = new Monkey("大猴子~~");//OK T=Object R=Object M=Object
g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是 Object 子类
System.out.println(g2);
}
}
//解读
// 1. Monkey 后面泛型,所以我们把 Monkey 就称为自定义泛型类
// 2, T, R, M 泛型的标识符, 一般是单个大写字母
// 3. 泛型标识符可以有多个.
// 4. 普通成员可以使用泛型 (属性、方法)
// 5. 使用泛型的数组,不能初始化
// 6. 静态方法中不能使用类的泛型
class Monkey<R, M,T> {
String name;
//属性使用到泛型
R r;
M m;
T t;
//因为数组在 new 不能确定 T 的类型,就无法在内存开空间
T[] ts;
public Monkey(String name) {
this.name = name;
}
public Monkey(R r, M m, T t) {//构造器使用泛型
this.r = r;
this.m = m;
this.t = t;
}
public Monkey(String name, R r, M m, T t) {//构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
//因为静态是和类相关的,在类加载时,对象还没有创建
//所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
// static R r2;
// public static void m1(M m){
//
// }
//方法使用泛型
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public R getR() {
return r;
}
public void setR(R r) {//方法使用到泛型
this.r = r;
}
public M getM() {//返回类型可以使用泛型.
return m;
}
public void setM(M m) {
this.m = m;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", r=" + r +
", m=" + m +
", t=" + t +
", ts=" + Arrays.toString(ts) +
'}';
}
}
5.2 自定义泛型接口
➢基本语法
interface 接口名<T, R…> {}
注意细节
1)接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
2)泛型接口的类型,在继承接口或者实现接口时确定
3)没有指定类型, 默认为0bject
package hsp.genericity_;
public class GenericityInterface01 {
public static void main(String[] args) {
}
}
/*
* 泛型接口使用的说明
* 1. 接口中,静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
* 3. 没有指定类型,默认为 Object
*/
interface USB<U, R> {
int n = 10;
//U name; 不能这样使
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在 jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
//在继承接口 指定泛型接口的类型
interface IA extends USB<String, Double> {
}
//当我们去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了 U 为 String R 为 Double
//,在实现 USB 接口的方法时,使用 String 替换 U, 是 Double 替换
class AA implements IA{
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
//给 U 指定 Integer 给 R 指定了 Float
//所以,当我们实现 USB 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements USB<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型,默认为 Object
//建议直接写成 IUsb<Object,Object>
class CC implements USB {
等价 class CC implements IUsb<Object,Object>
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
5.3 自定义泛型方法
15.6.3自定义泛型方法.
➢基本语法
修饰符<T.R…>返回类型方法名(参数列表) { }
➢注意细节
1.泛型方法,可以定义在普通类中,也可以定义在泛型类中
2.当泛型方法被调用时,类型会确定
3. public void eat(E e) {}, 修饰符后没有<T,R…> ,eat方法不是泛型方法,而是使用了泛型
package hsp.genericity_;
import java.util.ArrayList;
public class GenericityMethod01 {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
System.out.println("============================");
car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型
//测试
//T->String, R-> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(),12.12);
}
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
public void run() {//普通方法
}
//说明 泛型方法
//1. <T,R> 就是泛型
//2. 是提供给 fly 使用的
public <T, R> void fly(T t, R r) {//泛型方法
System.out.println(t.getClass());//String
System.out.println(r.getClass());//Integer
}
}
class Fish<T, R> {//泛型类
public void run() {//普通方法
}
public <U, M> void eat(U u, M m) {//泛型方法
}
//说明
//1. 下面 hi 方法不是泛型方法
//2. 是 hi 方法使用了类声明的 泛型
public void hi(T t) {
}
//泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
public <K> void hello(R r, K k) {
System.out.println(r.getClass());//ArrayList
System.out.println(k.getClass());//Float
}
}
6泛型的继承和通配符
61泛型的继承和通配符说明GenericExtends.java
1)泛型不具备继承性
List list = new ArrayList 0; //对吗?
2) <?> :支持任意泛型类型
3) <? extends A>:支持A类以及A类的子类,规定了泛型的上限
4) <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
package hsp.genericity_;
import java.util.ArrayList;
import java.util.List;
//泛型的继承和通配符
public class GenericWildcard01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Object o = new String("xx");
//泛型没有继承性
//List<Object> list = new ArrayList<String>();
//举例说明下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AAA> list3 = new ArrayList<>();
List<BBB> list4 = new ArrayList<>();
List<CCC> list5 = new ArrayList<>();
//如果是 List<?> c , 可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
//List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
printCollection3(list1);//√
//printCollection3(list2);//×
printCollection3(list3);//√
//printCollection3(list4);//×
//printCollection3(list5);//×
}
//说明: List<?> 表示 任意的泛型类型都可以接受
public static void printCollection1(List<?> c) {
for (Object object : c) { // 通配符,取出时,就是 Object
System.out.println(object);
}
}
// ? extends AAA 表示 上限,可以接受 AAA 或者 AAA 子类
public static void printCollection2(List<? extends AAA> c) {
for (Object object : c) {
System.out.println(object);
}
}
// ? super 子类类名 AAA:支持 AAA 类以及 AAA 类的父类,不限于直接父类,
//规定了泛型的下限
public static void printCollection3(List<? super AAA> c) {
for (Object object : c) {
System.out.println(object);
}
}
}
class AAA {
}
class BBB extends AAA {
}
class CCC extends BBB {
}
7.JUnit
7.1 为什么需要 JUnit
1.一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
2.如果有多个功能代码测试,就需要来回注销,切换很麻烦
3.如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了-> JUnit
7.2 基本介绍
1.JUnit是一个Java语言的单元测试框架
2.多数Java的开发环境都已经集成了JUnit作为单元测试的工具
public class JUnit_ {
public static void main(String[] args) {
//传统方式
//new JUnit_().m1();
//new JUnit_().m2();
}
@Test
public void m1() {
System.out.println("m1 方法被调用");
}
@Test
public void m2() {
System.out.println("m2 方法被调用");
}
@Test
public void m3() {
System.out.println("m3 方法被调用");
}
}