Java SE
1.Java基础
1.1Java简介与安装
1.Java之父 詹姆斯-高斯林 Sun公司 2009被Oracle收购
2.Java8(2014发行)是目前企业中广泛采用的版本
3.Java特点:跨平台、面向对象、简单性(GC内存管理)
注:GC是垃圾回收(Garbage Collect)的缩写
程序是为了模拟现实世界解决现实问题而使用计算机语言编写的一系列有序的指令集合
1.2DOS命令(常用)
查看当前目录下文件及文件夹(directory)dir
更换盘 d: g: h:
进入文件夹 cd 文件夹名字
返回上一级 cd …
清空屏幕 cls (clear screen)
删除文件 del 文件名
删除文件夹 rd 文件夹名称
退出 exit
回响得到连接节点路径 echo %JAVA_HOME%(就会得到配置的目录)
文件名之间最好不要有空格
1.3核心术语
- JVM(Java Virtual Machine)虚拟机:使用软件在不同操作系统中,模拟相同的环境。
一个Java进程(程序)对应一个JVM - JRE(Java Runtime Environment)运行环境:包含JVM和解释器,完整的Java运行环境。
如果仅运行Java程序,只需要jre - JDK(Java Development Kit)开发环境:包含JRE + 类库 + 开发工具包(编译器+调试工具)。
如果需要编写Java程序,必需要JDK
1.4类的定义规则
- 同一个源文件中可以定义多个类。
- 编译后,每个类都会生成独立的 .class文件。
- 一个类中,只能有一个主方法,每个类都可以有自己的主方法。
- public修饰的类称为公开类,要求类名必须与文件名称完全相同,包括大小写。
- 一个源文件中,只能有一个公开类。
1.5注释规范
格式
单行注释: //注释信息
多行注释: /*注释信息*/
文档注释: /**注释信息*/
1.6命名规范
标识符:为各种变量、方法、类、属性起的名字称为标识符
语法和约定俗成的规定:
-
可以由:字母、数字、_、$ 组成,但不能以数字开头。
- 不能与关键字、保留字重名。
-
类名由一个或多个单词组成,每个单词首字母大写。例:class HelloWorld{}
- 函数名、变量名由一个或多个单词组成,首单词首字母小写,拼接词首字母大写。 例:helloWorldDemoOne
- 包名全小写,只可以使用特殊字符“.”,并且不以“.”开头或结尾。
- 常量全大写,多个单词用 _ 连接。例: HELLO_ONE MATH_PI=3.14
- 项目名:hello-java 不要使用 _ 连接
1.7Java关键字
关键字都是小写的

进制转换
八进制转二进制 例:7 6 4 转为 111 110 100 每位数字拆成 三位二进制
二进制转八进制 1 100 101 011 转为1453 三位二进制一起看
十六进制 0-10ABCDEF F代表的是15 0x开头代表它是十六进制的 四位一起看
小数二进制

正数原反补码一样
负数 原码是正数原码符号位为1,反码是符号位不变其他位不变,补码为反码+1
utf32全部用4个字节 ,utf16用2或4个字节进行存储,utf8灵活很多(java默认)
1.8常量与变量
常量包括 :
字符串/整数/小数/字符/布尔/空 常量
空常量值为null 字符串双引号 字符单引号 布尔只有true和false两种
变量:计算机内存中的一块存储单元,是存放数据的基本单元,由 数据类型 变量名 值组成
三个步骤:1、声明,开辟变量空间;2、赋值,将数值赋给变量;3、应用
long类型变量定义时为防止整数过大后面要加L,float类型变量定义时为防止类型不兼容后面要加F
*Java是强类型语言,变量类型必须与数据类型一致
1.9Java数据类型
基本数据类型(8个):
整型byte short int long
小数:float double
布尔:boolean
字符类型 :char
引用数据类型
字符串String:多个字符组成的序列
数组
对象
判断字符串相等的方法:
1.比较是否同一个位置:使用 ==
2.比较内容是否相民: 使用 equals方法
1.10类型转换
自动类型转换(类型兼容,目标类型>源类型)
表示数据范围从大到小
double >float> long > int > char 、short、byte
强制类型转换(比较灵活,但有截断风险)
语法是:(目标类型)变量
例如: short s=100; int i=(int)s;
也可以通过**a+" ",通过‘+’链接,将整型强制转换一下字符串。 **
**String str=a[0]+“”;**其中a[0]是int型
1.11运算符
算术运算符:±*/% ++ –
关系运算符:> < >= <= == !=
赋值运算符:= += -= /= *= %=
逻辑运算符:&& || ! (与或非)
位运算符:& |
三元运算符: 布尔表达式? 结果1 :结果2
如果值为true,返回为结果1的值,值为false则返回结果2的值
*逻辑运算符(也叫 短路逻辑运算符)中
若判别式左边为false那么在&&中就不判别右边表达式直接结果为false,
若判别式左边为true那么在||中就不判别右边表达式直接结果为true
ASCII表

字符的+操作
是拿字符在计算机底层对应的数值来进行计算的
例 int i=10; char c =‘a’; System.out.println(i+c); 打印出来的是107,因为’a’对应的是97
char ch=i+c;那么char类型的ch会被自动提升为int类型
字符串的+操作
+操作出现在字符串时 +代表的是字符串拼接符
System.out.println("it"+"ti"); // itti 字符串的拼接
System.out.println("ti"+666); // ti666 字符串和int型拼接也可以
System.out.println("ti"+6+66); // ti666 字符串在前先转换成了字符串类型
System.out.println(6+66+"ti"); // 72ti 字符串在后int型先相加再转化
2.条件选择、循环
任何变成语言都有三种控制语句:
1、顺序控制语句 2、条件控制语句 3、循环控制语句
2.1条件分支if
//综合
if(布尔表达式){ //为true执行
if(布尔表达式2){ //再判断
代码块1
}else{代码块4}
} else { //为false执行
if(布尔表达式3){ //再判断
代码块2
}else{代码块5}
}
2.2switch语句
switch(表达式){
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
case 值3:
语句体3;
break;
...
default:
语句体n+1;
}
switch使用细节:
1、在switch中值的类型:byte short int char String 枚举类型
2、在switch中case值不允许重复
3、在switch中break表示结束当前switch语句
break可以省略不写,如果省略那么switch会一直往下执行,直到遇到下一个break;
4、switch和多重if的区别:
多重if中可以做关系运算,在swtich中只能做等值判断
2.3while语句
public static void main(String[] args) {
//罚抄作业一百遍
//1.定义循环变量
int i=1;
//2.定义循环条件
while(i<=100) {
//3.循环体
System.out.println("抄写"+i+"篇");
//4.更新循环变量,以便下次继续循环或者退出循环
i++;
}
}
while的特点:
while最少执行0次 最多执行无数次
2.4do-while语句
语法:
do{
逻辑代码(循环操作)
}while(布尔表达式);
执行流程:
- 先执行一次循环操作之后,再进行布尔表达式的判断。
- 如果结果为true,则再次执行循环操作。
- 如果结果为false,才会退出循环结构,执行后续代码。
最少运行1次,最多运行n次
2.5for循环
for(初始化部分;循环条件;迭代部分){
循环操作
}
for(int i=1;i<=100;i++) {
System.out.println(i);
}
2.6break和continue语句
1.break 表示中断循环,退出整个循环
2.continue:表示跳过当次循体内容执行,继续下一次执行
2.7Random随机数
使用步骤:
1:导包 2:创建对象 3:获取随机数
import java.util.Random;
Random r = new Random();
int num = r.nextInt(10); //获取数据范围 [0,10) 包括0但不包括10
//如果要获取0-10之间的随机数可以
//int num = r.nextInt(10)+1;
2.8日期方法调用
通过Date对象直接获取星期几
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HelloWorld {
public static void main (String[] args) throws ParseException {
Date date=new SimpleDateFormat("yyyy-MM-dd").parse("2022-03-28");
//EEEE从日期对象中获取week信息
System.out.println(new SimpleDateFormat("EEEE").format(date));
}
}
3.数组 方法 函数
3.1数组
定义:
数组是一组连续的存储空间,存储多个相同数据类型的值。
特点:
1.数组中的元素类型相同
2.数组的长度是固定的。当创建一个数组时,就会申请一块内存空间。
3.数组中的元素都是相邻
类型:引用类型 所以在创建数组这种类型需要使用new关键字
int[] a=new int[5];
int[] 表示整型数组
long[] 表示长整型数组
String[] 表示字符串数组,里面存储都是字符串
System.out.println(a); //[I@7852e922 打印出来的是数组的引用(内存地址)
//字符串型数组
String[] s=new String[3];
System.out.println(s); //[Ljava.lang.String;@4e25154f
3.1.1内存分配

3.1.2动态/静态初始化
//动态初始化 a是数组名,代表了数组在内存中的首地址
int[] a=new int[5];
//静态初始化 在声明数组的同时进行全部元素的初始化
String[] s=new String[]{"hello","world","java","haha"};
3.1.3常见问题及操作
1、注意防止下标越界。下标不能超过数组的(长度-1)
2、空指针异常:访问的数组已经不再指向堆内存的数据,造成空指针异常
System.out.println(s.length); //length是数组的api之一,可以得到数组的长度
//数组的遍历
String[] s=new String[]{"hello","world","java","haha"};
for(int i=0;i <s.length;i++) {
System.out.println(s[i]);
}
//获取最值
1、定义一个变量用于保存最大值(最小值)
2、取数组中第一个数据作为变量的初始值
3、与数组中剩余的数据逐个比较,每次比对将最大值保存到变量中
4、循环结束后打印变量的值
3.1.4数组的扩容
方式一
int[] nums=new int[5];
nums[0]=11;
nums[1]=22;
nums[2]=33;
nums[3]=44;
nums[4]=55;
//1.创建容量更大的新的数组
int[] newNums=new int[nums.length*2];
//2.复制old数组中的元素到new数组中
for(int i=0;i<nums.length;i++) {
newNums[i]=nums[i];
}
//3.将old数组指向new数组
nums=newNums; //nums.length=10
nums[5]=110;
nums[6]=220;
nums[7]=330;
nums[8]=440;
nums[9]=550;
方式二
System.arraycopy(原数组,原数组起始,新数组,新数组起始,长度) 更灵活
例:System.arraycopy(nums,0,newNums,0,5);
方式三
java.util.Arrays.copyOf(原数组,新的数组的长度) 返回新的数组 更方便
Arrays提供一系列对数组操作的实用方法。
int[] nums=new int[] {11,22,33,44,55}; //老数组
int[] newNums=java.util.Arrays.copyOf(nums, 10);
for(int i=0;i<newNums.length;i++) {
System.out.println(newNums[i]);
}
3.1.5作为参数 返回值 方法可变长参数
作为参数
参数传递方式
1.按值传递 :基本类型+String都是按值传递
2.按地址传递:除String外的所有引用类型,数组便是一个引用类型
*String 也是按值传递
方法调用后按值传递的仍然不会改变原始变量的值;按地址传递的,方法的参数传递的是变量的地址所以会改变值
作为返回值
按引用(地址)传递时,如果在方法内部,将形参重新指向别的地址,对实参将失去控制。此时可以考虑使用引用类型(数组)作为返回值改变实参(让实参变量重新指向方法返回值)。
int[] oa= {1,2,3,4,5};
oa=expand2(oa);
System.out.println("调用expand2后oa的长度:"+oa.length);
public static int[] expand2(int[] oa) {
int[] na=new int[oa.length*2];
System.arraycopy(oa, 0, na, 0, 5);
return na;
}
方法可变长参数
语法: 类型…参数名
规则:必须在形参列表的最后,且只有一个可变参数
在具有可变长参数的方法中可以把参数当成数组使用
//可变长参数的方法,args可以接收1个或者多个参数,args可以当作数组使用
public static int addParams(int... args) {
int sum=0;
for (int i = 0; i < args.length; i++) {
sum+=args[i];
}
return sum;
}
//可变参数必须是最后一个
public static int addParams2(int length,int... args) {
int sum=0;
for (int i = 0; i < args.length; i++) {
sum+=args[i];
}
return sum;
}
3.1.6二维数组
从逻辑上来看,二维数组是由行,列组成的表格。
从物理上来看,二维维组是每个元素都是数组的一维数组。
3.1.7JDK排序
java.util.Arrays.sort(数组名)。
快速排序(api)
int[] nums={4,1,3,5,2}
java.util.Arrays.sort(nums)
3.2方法
3.2.1什么是方法
方法,又名函数,又叫行为
高内聚.低耦合,将重复的代码进行封装,可以反复使用,那么封装之后得到就是方法
Java的方法类似于其它语言的函数,是一段用来完成特定功能的代码片段。
格式:
访问权限修饰符 其他修饰符 返回值类型 方法名称(参数列表) {
//方法体【函数体】
return 返回值;
}
/**
* public 权限修饰符,后面会讲到他是来控制权限,现在默认就给他加上
* static 静态修饰符,可加可不加(目前默认你就加就可以)
* 加了static,在main方法中调用该方法直接使用方法名();
* 不加static,在main方法中就不能直接使用方法名来调用了
* 1.当前类的类名 标识符=new 当前类的类名();
* Demo d=new Demo();
* 2.标识符.方法名();
* d.add(参数);
* void 返回值
* void:没有返回值
* 返回值的类型
* 方法名:标识符
* ()内:参数列表,如果有,那么就写,没有就不写
* {} 方法的具体实现
*
*/
3.2.2声明方法的意义
1把可复用的逻辑抽取出来,封装成方法,提高代码的重用性。DRY(Don't Repeat Yourself)原则,
2实现相对独立、赋值的逻辑
3可以对具体实现进行隐藏/封装
- 减少代码冗余。
- 提高复用性。
- 提高可读性。
- 提高可维护性。
- 方便分工合作。
3.2.3方法的调用
//无参
方法名()
//有参
方法名(实参,...)
1.参数列表,所给的值和参数列表要一一对应
2.static,如果有,那么使用类名去调用,没有的话,需要出现new关键字
3.看有没有返回类型,如果是void,那么直接调用就行,如果不是,那么必须要是有该返回类型的变量来接收
3.2.4方法的参数
//int x 就是一个形式参数 包括参数的类型,参数的名称
public static void m1(int x){
//在下面整个方法体中,x均有效
}
//调用时
方法名称( 实际参数 );
m1(5)
3.2.5方法的返回值
continue<break<return
返回值:有一些方法,他是对数据做相关的处理,我将数据交给方法执行之后,我需要得到这个处理结果
eg:我想写一个判断是否是闰年
由于是不是闰年对于我接下来的逻辑判断很重要,所有我希望拿到处理结果
这时,就需要通过方法的返回值来拿到结果
方法名前面一个单词表示的就是返回值
当没有返回值,就是用void就可以了
但是需要返回值的时候,那么就需要在该位置上写上返回值的类型
但是此时可能方法会报错,所以一定要加上return去返回给类型的值(可以是默认值)
注意:如果方法体内有逻辑判断,你需要保证每一种情况都要有返回值return出去
3.2.5方法的进阶
重载
多态
同一种事物的不同表现形式(H2O)
java允许同一个方法对不同的调用条件作出不同的反应(多态)
同一个类中,方法名字相同,参数列表不同,则是方法重载。
1.参数列表的个数不同
2.参数的类型不一样
3.参数类型的顺序不一样
4.与返回值无关
递归
什么是递归?
- 解决具有既定规律的问题时,在方法内部再次调用自身方法的一种编程方式。
何时使用递归?
- 当需要解决的问题可以拆分成若干个小问题,大小问题的解决方式相同,方法中自己调用自己。
- 使用循环解决的常规问题,都可以替换为递归解决。
求阶乘:f(n)=f(n-1)*n
如何正确使用递归?
- 设置有效的出口条件,可以让调用链上的每个方法都可以正确返回,避免无穷递归。
- 写递归方法时,总是先定义出口
例:斐波那契数列 0 1 1 2 3 5
从第3位起满足这个规则
f(n)=f(n-1)+f(n-2)
public static int f(int n){
if(n==1)
return 0;
else if(n==2)
return 1;
else{
return f(n-1)+f(n-2);
}
}
4.面向对象
4.0思想
案例二:小明是一个电脑小白,想要配一台电脑,买完零件后需要运到家里,组装完成后打开电脑玩游戏
面向过程 面向对象
1.小明补充电脑知识 1.让一个懂电脑的朋友(老王)把配置电脑所需的文件列出来
2.小明去买零件 2.委托一个能跑腿的人去买零件
3.小明把零件带回家里 3.委托一个快递小哥帮小明送到家里
4.小明组装电脑 4.委托一个会组装电脑的人帮小明组装电脑
5.小明开机玩电脑 5.小明自己打开电脑,开始玩游戏
面向对象与面向过程区别
a.都是看待问题的一种思维方式,都能解决问题
b.面向过程着眼于所有的事情按照步骤来实现
c.面向对象着眼于找到一个具有特殊功能的对象,委托这个对象去做某件事情。
注意:面向对象是一种思想,并不是一门编程语言。
随着时代的发展,面向对象必然是趋势
4.1类的定义
将某一类事物共同的特征提取出来作为模板,这个模板称为类(Class)
类是对象的抽象,是多个对象之间的共性
public class 类名{
属性 //或者称:类成员变量,类的(私有)字段,对象的实例变量
方法 //类的实例方法,类成员方法
}
类的属性在定义时可以不用初始化,会自动赋值初始值。(但不是一个好习惯)
String :null
int : 0
boolean: false
char: '' //空字符 \u0000
注意:1.一个源文件(.java)中可以包含多个类(具体写代码的过程,对大家要求一个.java文件中只能有一个class)代码>规范>约定
2.但是只能有有一个类前面能加public,并且被public所修饰的类名必须和源文件名相同
3.类是java的执行单位,无论一个源文件中有多少个class,编译之后的.class会和源文件中的class一一对应
4.2对象的创建及理解
必须根据类创建对象的实例(对象是类的具体实例)
//类的实例对象,或者类的实例=对象
//生产一条哈土奇
Dog hsq=new Dog(); //Dog是类,hsq是实例对象,new是关键字,Dog必须和class //名相同
hsq.breed="哈士奇";
hsq.sex="公";
hsq.color="黑白";
//访问对象的属性和方法
System.out.println(hsq.breed);
System.out.println(hsq.sex);
System.out.println(hsq.color);
hsq.eat();
hsq.sleep();
总结:
int a=1;
int表示的数据类型
a=表示的是该类型的变量
1具体常量
由于1是满足int锁定义的数据类型的规范,所以可以将它赋值给int类型的变量
类比:
Dog类就是定义一种有breed,sex,color这些值的数据类型
hsq…就是只满足Dog数据类型的数据
只要出现new关键字都会在堆内存中重新去开辟一块内存地址
Dog为数据类型, hsq 为变量 对象的内存地址 ,new 为关键字 ,Dog()为构造方法
-
凡是new出来的,都是会在内存中开辟地址空间,都是一个新的对象
-
引用不等同于对象,只能在一定程度上可以代表一个对象
-
类===》数据类型
4.3实例变量和局部变量的区别

4.4实例方法与类方法
区别就是:前面有无关键字static
1.实例方法只能被对象调用
2.类方法是static方法,可以被类名和对象调用
4.5方法重载
对象的同一种行为可能存在多种实现过程。
从表面上看,在一个类中定义多个同名方法,即为方法重载。
方法重载规则:
1.方法名相同
2.参数列表不同(类型、个数、顺序)。 小结:只要在调用时能够明确知道是哪个方法即可,不允许出现歧义。
3.与访问修饰符、返回值类型无关
public void sayHi() {
System.out.println("您好");
}
//正确的重载:方法的参数个数不同
public void sayHi(String name) {
System.out.println("hello" + name);
}
//正确的重载:方法的参数类型不同
public void sayHi(int num) {
for(int i=0;i<num;i++)
System.out.println("hello" );
}
//正确的重载:方法的参数个数不同
public void sayHi(String name,int n2,int n3) {
System.out.println("hello" + name + n2 +n3 );
}
//正确的重载:方法参数的顺序不同
public void sayHi(int n3,int name,String n2) {
System.out.println("hello" + name + n2 +n3 );
}
//非法的重载:返回值不同不是区分重载的规则。
// public int sayHi(int n3,int name,String n2) {
// System.out.println("hello" + name + n2 +n3 );
// return 0;
// }
4.6构造方法 对象创建过程
是指通过类去构造一个实例对象的方法,构造方法是用来构造(初始化)对象
new Dog(); //都是构造方法
new Strudent();
所有类默认都有无参的构造方法。但如果你一旦重载构造方法,默认的构造方法就没有了,此时必须显示定义无参构造方法。
语法:
[权限修饰符] 方法名(参数列表){ //如public Dog(){ }
方法体;
}
注意:
1.方法名必须和类名完全一致,构造方法不是使用对象去调用的,他只能通过new关键字调用,他的作用就是在堆内存中开辟一块内存空间,来定义该引用数据类型的数据
2如果类中没有自定义构造方法,系统默认给我们提供了一个公有的无参构造方法
public Dog(){
}
无参的构造方法是构造了一个全是默认值的对象
3.如果写了构造方法,那么系统则不会提供该方法
对象的创建过程:
- 内存中开辟对象空间
- 为各个属性赋予初始值
- 执行构造方法中的代码
- [将对象的地址赋值给变量]
4.7this关键字
this指代 调用当前方法的那个对象
在类内部,访问类的属性和方法都可以加上this; this.属性 this.方法()
public class Dog(){
String name="";
public Dog(){}
public Dog(String name){
this.name=name; //表示当前对象的name属性 ,this表示此类当前的对象
}
}
调用本类中的其他构造方法。如:this()、this(实参)。 ==必须在构造方法的首行==
public class Dog(){
String name="";
int age=0;
public Dog(){}
public Dog(String name){
this.name=name; //表示当前对象的name属性 ,this表示此类当前的对象
}
public Dog(String name,int age){
this(name); //调用本类中的其他构造方法。如:this()、this(实参)。
this.age=age;
}
}
4.8类和对象的写法
1.思考该类的静态属性,在类中设计好变量,(考虑颗粒度,不需要写的太细,也不要写的太普遍)
2.为了后期测试方便,给两个构造方法,一个有参,一个无参,构造方法的重载
可以自定生成,右键==》source==>倒数第三个,选择对应的属性,来生成两个构造方法
(super()可删可不删,不影响使用)
3.设计两到三个行为
4.自动生成一个类似于自我介绍的方法,方便后面查看数据
右键==》source==》Generate toString...
例:
public class Cat {
String color;//颜色
String type;//品种
String name;//姓名
char sex;//性别
int age;//年龄
public Cat(String color, String type, String name, char sex, int age) {
super();
this.color = color;
this.type = type;
this.name = name;
this.sex = sex;
this.age = age;
}
public Cat() {
super();
}
//抓老鼠
public void catchMouser(){
System.out.println(this.name+"正在抓老鼠");
}
//陪主人玩
public void playWithMaster(){
System.out.println(this.name+"陪主人玩");
}
//自我介绍,方便后面测试时,打印数据
@Override
public String toString() {
return "Cat [color=" + color + ", type=" + type + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}
public class CatTest {
public static void main(String[] args) {
Cat c=new Cat("灰色", "英短", "雪碧", '公', 2);
String str=c.toString();
System.out.println(str);
c.catchMouser();
Cat c1=new Cat("橘色", "加菲", "芬达", '母', 1);
//自动补全 ctrl+1
String string = c1.toString();
System.out.println(string);
Cat c2=new Cat("橘色", "加菲", "芬达", '母', 1);
}
}
4.9匿名对象(了解)
没有名字的对象
new Flower("白色", "栀子花", true);
4.10引用数据类型使用和访问权限修饰符
引用数据类型:就是我们自己写的类,对象就是数据
基本数据类型:八个基本数据类型,具体的值就是数据
Person p=new Person();
int a=1;
基本数据类型能做的事,引用数据类型也能做
1.引用数据类型的内存分析
1.内存模型从大的方面可以分为堆和栈
2.栈内存存值有顺序的,而基本数据类型的值就是存放在栈内存中
3.堆内存存值是离散的,引用数据类型的值就是在堆内存随机开辟的
4.java中所有的参数传递都是值传递,值传递的本质是内存地址的传递
形参为基本数据类型时,形参一旦发生修改,那么就指向了新的内存地址,而对原来的内存地址中的值没有影响
形参是引用数据类型时,只要形参没有重新new对象去指向新的内存地址,那么此时形参和实参都是指向同一个内存地址,形参一旦修改内存地址中值,实参也会受影响
5.以前会有方法区的概念,现在都在淡化这样的概念
1.引用数据类型作为参数进行传递时
引用数据类型作为参数进行传递时,形参可以改变实参的内容,但是不能该改变实参对象本身
public static void changeName(Flower f){
f.color="白色";
}
public static void main(String[] args) {
Flower f1=new Flower();
f1.brand="牡丹";
f1.color="黄色";
f1.isSweet=true;
f1.showInfo();//花名:牡丹,颜色:黄色,香吗?true
FloweTest.changeName(f1);
f1.showInfo();//花名:牡丹,颜色:白色,香吗?true
}
2.引用数据类型作为属性进行声明时
当引用数据类型作为类的属性时,则描述了该类的对象有什么类型的数据
比如:人有花,人有狗,学生有书
//花类
public class Flower {
String color;//颜色
String brand;//品种
boolean isSweet;//香
}
public class Person {
String name;//姓名
int age;//年龄
char sex;//性别
Flower f;//有朵花
public Person() {
super();
}
public Person(String name, int age, char sex, Flower f) {
super();
this.name = name;
this.age = age;
this.sex = sex;
this.f = f;
}
public void eat(){
System.out.println("正在吃饭");
}
//送花
public void sendFlower(Person p){
p.f=this.f;//把我张三的花给林志玲
this.f=null;//把自己的花设为空
}
public void showInfo(){
System.out.println("姓名:"+this.name+",年龄:"+this.age+",性别:"+this.sex+",花:"+this.f);
}
3.引用数据类型作为方法返回值
直接用该返回值类型的变量接收即可
4.访问权限修饰符
Person p=new Person();
1.将变量直接暴露给用户来随便赋值,安全性太差
p.name="傻X";
2.为了防止用户给变量赋上不应该赋的值,所以我们需要将主动性掌握在开发者手里,那么此时可以使用private关键字去私有化我们的变量,使得变量的可见范围仅存在在当前类中才能看到
p.name就会报错
3.既然我们设计这个属性,我们初衷是让用户使用,但是又不想让用户随便使用
只能向外去提供公有的方法给用户赋值(setXXX()),取值(getXXX())
4.这时主动权就掌握在我们开发者手里了
public void setName(String name){
if("傻X".equals(name)){
System.out.println("别乱来");
}else{
this.name=name;
}
}
- 权限修饰符可以修饰类,属性,方法,从而来控制他们的可见范围
- public 所修饰称为公有的,任何地方都可以看得到(同一个类,同一个包,不同的包)
- private 所修饰称为私有的,只要同一个类下面可以看到(同一个类)
- 默认的,什么都不写,只有在同一个类和同一个包下面可以访问(同一个类,同一个包)
| 权限\使用范围 | 本类 | 同包中类或同包子类 | 不同包子类 | 不同包类 |
|---|---|---|---|---|
| public公有的 | v | v | v | v |
| protected受保护的 | v | v | v | x |
| 默认[default] | v | v | x | x |
| private私有的 | v | x | x | x |
5.封装 继承 多态 重写
5.1封装
JavaBean:属性私有化,向外提供公有的get/set方法
什么时候将类设计成javaBean?
1.你所设计的类关注点是在属性还是行为、功能
2.考虑你所设计的类用途是否更多的偏向于数据类型
步骤
1.将属性私有化
2.两个重载构造方法
3.对属性生成公有的get和set方法
4.设计行为(没有特殊要求,不要设计)
5.重写toString();
public class Person {
private String name;//姓名
private int age;//年龄
private char sex;//性别
public Person() {
super();
}
public Person(String name, int age, char sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public void eat(){
System.out.println("正在吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
解释:
-
set方法是用来赋值的,等同于之前的p.属性=值
-
get方法是用来获取值的,等同于p.属性
//打印对象的内容时,如果你在类中重写了toString();那么会直接打印他的内容
//如果没有重写toString();那么会打印该对象的内存地址(hashCode())
5.2对象数组
数据类型为类类型的对象
(注意:封装get方法的运用、Person p=ps[j]与基本数据类型方法同样 )
public class Demo {
public static void main(String[] args) {
Person p1=new Person("张三1", 12, '男');
Person p2=new Person("张三2", 21, '女');
Person p3=new Person("张三3", 18, '男');
Person p4=new Person("张三4", 16, '男');
Person p5=new Person("李四", 11, '女');
Person[] ps={p1,p2,p3,p4,p5};
//找出ps对象数组中的男女人数
int manNum=0;
for (int i = 0; i < ps.length; i++) {
if('男'==ps[i].getSex()){
manNum++;
}
}
System.out.println(manNum);
System.out.println(ps.length-manNum);
for (int i = 0; i < ps.length; i++) {
System.out.println(ps[i].toString());
}
//根据年龄进行升序排序
for (int i = 0; i < ps.length-1; i++) {
for (int j = 0; j < ps.length-1-i; j++) {
if(ps[j+1].getAge()<ps[j].getAge()){
Person p=ps[j];
ps[j]=ps[j+1];
ps[j+1]=p;
}
}
}
System.out.println("排序完....");
for (int i = 0; i < ps.length; i++) {
System.out.println(ps[i].toString());
}
}
}
5.3变量的取值范围
(变量的取值范围是:离他最近的大括号)
如果是形参,那么取值范围就是离他最近的右大括号范围内
如果不是形参,那么取值范围就是离他最近的左大括号范围内
所有的变量必须声明并且初始化之后才能使用
解释:
变量是声明再栈内存中的,他是去执行某一个值的内存地址,两者必须同时存在
所有的变量必须要声明之后赋值才能使用
成员变量(类下面),他会在构造方法初始化的时候进行赋值
局部变量,没有构造方法进行初始化,所以需要手动赋值
《think of java》,变量在声明的时候都会去指向一个默认值,而局部变量没有去指向默认值的原因是:程序员在设计局部变量的时候,肯定是要考虑给局部变量赋值,所以java没有给局部变量赋值的原因就是一种善意提醒,提醒程序员给变量赋值
栈:栈内存存值是由顺序的,变量和基本数据类型的值都是存储在栈内存中的
堆:堆内存存值是无序的,所以引用数据类型的值都是存储在堆内存中
方法区:数据区,产量去,类区,静态区
Person p=new Person();为例

5.4static关键字
静态,可以修饰方法,称之为静态方法,又可以修饰属性,称之为静态属性
静态即是类
- 静态方法
又称之为类方法,他是类的组成,所以用类名去调用
用对象去调用也可以,但是会有警告
public static void eat(){
System.out.println("正在吃饭...")
}
- 静态属性
若在Person类中声明static int count;
Person.count
又称之为类属性,他是类的组成,所以用类名去调用
用对象去调用也可以,但是会有警告
总结:
静态方法(类方法),不可以直接发起对成员属性和成员方法的调用(有了类,不一定有对象)
成员方法能发起对静态属性和静态方法的调用(有了对象,一定有类)
静态方法什么时候用:一般只有在设计工具类的时候会去使用。
静态变量什么时候用:计数的时候
作用:
我们将相同的数据放到静态存储区内,并让静态存储区中的数据共享给所有对象,这样就可以大大减少了不必要的内存损耗。
- static 静态代码块
static{
System.out.println("类已经被加载到内存中运行了...");
}
静态代码块是用来把类加载到内存中使用的,在构造方法执行之前执行,
但只会执行一次,他对成员方法和成员变量都不能调用
用来做一些类启动的准备工作

5.4.1类加载
JVM首次使用某个类时,需通过CLASSPATH查找该类的.class文件
将.class文件对类的描述信息加载到内存中,进行保存。
如:包名,类名,父类,属性,方法,构造方法
类加载时触发静态代码块的执行(仅一次),执行在静态属性初始化之后
5.5继承
继承:
通过某一种关系(一般为血缘),发生的一种馈赠的行为
java中的继承他是描述一种类与类之间关系
继承:允许子类去继承父类的非私有属性和行为,能发生继承的条件是满足is a…(狗是动物)
- 实现:extends关键字去是实现
public class FatherClass{
//属性
//方法
}
public class ChildClass extends FatherClass {
//属性
//方法
}
- 继承的好处
1.减少代码冗余,提高代码的可复用性
2.允许开发人员创建出分等级层次的类
- 继承的特点
1.子类去继承父类的非私有属性和行为
2.私有属性和行为不能被继承
3.构造方法不能被继承
4.java中的继承都是单继承,可以实现多重继承(继承具有传递性)
现有四个方法:
子类的静态代码块a
子类的无参构造方法b
父类的静态代码块c
父类的无参构造方法d
当执行 new 子类();上述方法的执行顺序
先有类再有对象,先有父再有子
c-->a-->d-->b
- 如何发生继承
继承的是属性和行为,属性和行为有属于对象,所以我们可以说继承是发生在对象层面上的,子类对象被new出来后,子类对象就会有父类的属性和行为,那么此时必然会有一个匿名父类对象被提前new出来让子类去继承
5.5super关键字
和this关键字使用类似,super就是调用当前方法的那个对象的父类对象
1.可以调用父类的属性和行为
2。super()可以调用父类的构造方法,当时此句如果出现在子类构造方法中,必须是第一句
3.当子类构造方法中没有super();系统会默认添加一个到第一位
可以用super调用私有方法,例如price就用 super.getPrice()
5.6方法的重写
在继承过程中,子类中从父类继承来的方法无法满足自己的需求时,可以在子类中对父类方法进行完善,这个完善过程叫做方法重写(override),方法的重写相当于在子类中覆盖父类中的方法。
- 重写的要求
1.首先要有继承
2.子类用自己的方式去改写了继承自父类的方法的方法体
- 重写的语法
1.方法上一定要有@Override这样的注解来标识该方法是重写
2.重写的方法不能拥有比父类方法更严格权限修饰符
3.重写的方法和被重写的方法具有相同的返回值,方法名,参数列表
jdk1.7之后,允许返回不一样,但是该返回值必须要有兼容性,必须都是引用数据类型(多态的思想)
public void eat(){
System.out.println("正在吃米饭...");
}
@Override
public void eat(){
System.out.println("正在吃肯德基...");
}
- 高频考点(默写)
override和overload
重写和重载
方法的重载:Overload,在同一个类中,方法名相同,参数类型,个数,顺序不同,互为重载方法。与返回值无关
方法的重写:Override,在两个类中,在继承过程中,在子类中重写父类中继承来的方法,方法名相同,参数列表、返回值必须相同,访问权限不能比父类严格。
重载时一种静态的多态,重写是一种动态的多态
5.7多态
所谓多态指的是同一种行为在不同的对象身上有不同的表现形式
同一个方法由于调用对象的不同而拥有不同的方法体
实现条件:
1.要有继承或者实现关系
2.要有重写
3.父类引用指向子类对象
Dog和Cat两个类都有eat();表现形式猫了饭后喵喵叫,狗吃了饭后汪汪叫,模拟人喂食的这个过程
现在多了一个动物叫鸭子,然后也要人去喂
在 人 类中添加feet的重载方法
// 喂狗
public void feet(Dog dog) {
dog.eat();
}
// 喂猫
public void feet(Cat cat) {
cat.eat();
}
// 喂鸭子
public void feet(Duck duck) {
duck.eat();
}
如果再领养XXX宠物,就需要给XXX喂食,怎么办?
这样频繁修改代码,代码可扩展性、可维护性差。使用多态优化。
多态使用
**添加Animal类,增加eat方法,让Dog,Cat,Duck继承Animal,重写eat方法
修改feet的方法
// 喂所有的
public void feet(Animal animal) {
animal.eat();
}
测试时
//父类引用指向子类对象
Animal c= new Cat("花花");
Animal d= new Dog("大黄");
Animal duck= new Duck("小黄鸭");
Person p= new Person("李雷");
//李雷去喂狗
p.feet(d);
p.feet(c);
p.feet(duck);
- 不同的引用调用的方法到底是如何调用的呢?
解释:所谓多态指的是同一种行为在不同的对象身上有不同的表现形式
指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
方法是属于对象的,看调用的方法是如何调用,主要看的是对象,而不是引用
多态使用形式:(父类引用指向子类对象)
- 1 使用父类类型作为方法形参实现多态
在上述例子中feet(Animal animal)中已体现
- 2 使用父类作为方法返回值实现多态
宠物商店类中有一个卖宠物的方法,根据用户输入的1.鸭子 2.小狗 3.猫咪,返回给用户不同的宠物
package Demo;
public class PetFactory {
/**
* 卖宠物的方法
* @param choice 选择
* @return 动物类型
*/
public Animal sellPet(int choice) {
Animal a = null;
switch (choice) {
case 1:
a = new Duck("小黄鸭");
break;
case 2:
a = new Dog("大黄");
break;
case 3:
a = new Cat("花花");
break;
}
return a;
}
}
测试
PetFactory pf=new PetFactory();
Animal a=pf.sellPet(2);
System.out.println(a.name);
练习:
第一步 新建一个Person类,包含name sex age,sleep();thinking();
第二步:新建宠物类Pet,name happyNum(体力值) 行为:eat();
第三步:新建狗类Dog,重写eat方法,吃东西会让happyNum值加3
第四步:新建狗类Cat,重写eat方法,吃东西会让happyNum值加2
第五步:扩展Person类,用来描述以下问题
张三有一个宠物狗,他喂了狗,狗happyNum加3
李四有一只宠物猫,她喂了猫,猫happyNum加2
新建测试类,测试上述过程
package Demo;
public class Person {
String name;// 姓名
char sex;// 性别
int age;// 年龄
Pet pet;//宠物
Person() {
}
Person(String name, char sex, int age,Pet pet) {
this.name = name;
this.age = age;
this.sex = sex;
this.pet=pet;
}
// 睡觉
public void sleep() {
System.out.println("正在睡觉");
}
// 思考
public void thinking() {
System.out.println("正在思考");
}
public void feet(Pet p){
System.out.println(this.name+"喂了"+p.name);
p.eat();
}
}
package Demo;
public class Pet {
String name;// 姓名
int happyNum;// 体力值
public Pet() {
super();
}
public Pet(String name, int happyNum) {
super();
this.name = name;
this.happyNum = happyNum;
}
/**
* 吃饭
*/
public void eat() {
System.out.println("吃饭...");
}
@Override
public String toString() {
return "Pet [name=" + name + ", happyNum=" + happyNum + "]";
}
}
package Demo;
public class Dog extends Pet{
Dog() {
}
Dog(String name,int happyNum) {
this.name = name;
this.happyNum=happyNum;
}
@Override
public void eat() {
System.out.println(this.name + "吃了骨头汪汪叫");
this.happyNum+=3;
}
}
package Demo;
public class Cat extends Pet{
Cat(){
}
Cat(String name,int happyNum){
this.name=name;
this.happyNum=happyNum;
}
@Override
public void eat(){
System.out.println(this.name+"吃了鱼喵喵叫");
this.happyNum+=2;
}
}
测试
package Demo;
public class Demo {
public static void main(String[] args) {
Pet d=new Dog("大黄", 100);
Pet c=new Cat("花花", 100);
Person p=new Person("张三", '男', 18, d);
Person p1=new Person("李四", '男', 18, c);
p.feet(d);
p1.feet(c);
System.out.println(d.toString());
System.out.println(c.toString());
}
}
5.8向上、向下转型
- 向上转型:会隐藏掉对象本身扩展的内容
Cat c = new Cat();
Animal a = c;//在向上转型的过程中,会隐藏掉对象本身独有的内容
Animal a = new Cat();
父类引用指向子类对象就是向上转型的过程
向上转型的优缺点:
优点:
1.解决了代码的冗余
2.提高了代码的可扩展性
缺点:
向上转型的过程中,会丢失掉子类独有的属性和行为
所以当你想找回子类被隐藏的属性和方法时,这时你需要做向下转向(强转)
什么类型的引用都可以转吗?
Animal a=new Dog();
if(a这个引用指向的对象是狗类型?){
Dog d=(Dog)a;
}else{
不能转
}
- 向下转型
1.指向父类对象的父类引用不能转换为子类类型
Animal a=new Animal();
Cat c=(Cat)a;
上述写法是错误的,会报java.lang.ClassCastException一个异常
编译不会报错,但是运行会报错
正确写法是
Animal a=new Cat();
Cat c=(Cat)a;
2.指向子类对象的父类引用能转换为该子类类型
Animal a=new Cat();
//a.upTree();//报错
//当你想拿回子类对象被隐藏的属性和方法时,需要做向下类型转换,(强制类型转换)
Cat c=(Cat) a;
c.upTree();//可以
- 总结
1.父类引用可以指向子类对象,但子类引用指向不了父类对象
2.把子类对象直接赋值给父类引用叫做向上转型,向上转型不用强制转化 Animal a= new Cat()
3.向上转型会丢失子类的特有方法和属性
4.把指向子类对象的父类引用赋值给子类叫做向下转型,必须要强制类型转换(前提是分能不能转型)
Animal a= new Cat()
Cat c=(Cat)a;
5.9instanceof关键字
是用来判断引用所指向的对象是不是某个类型
Animal a= new Cat();
//instanceof 返回类型为boolean
boolean b=a instanceof Cat;//b=true
boolean b=a instanceof Animal;//b=false
- 总结
在做向下转型时,强制要求必须要做instanceof判断
Animal a= new Cat();
if(a instanceof Cat){
Cat c=(Cat)a;
}else if(a instanceof Dog){
Dog d=(Dog)a;
}
5.10Object常用方法和final关键字
任何类都是继承于Object(万物皆对象)
导源码:window==>preferences===>java==>Installed jres==>选中,edit==>第二个rt.jar展开==》Sources attachment双击==》External file==>找到:C:\Program Files\Java\jdk1.8.0_91\sic.zip
查看源码:ctrl+shift+t
- equals() 判断内容是否相等
重写equals()
父类继承过来的equals方法不适合我做我想要的判断
@Override
public boolean equals(Object obj) {
Person p=null;
if(obj instanceof Person){
p=(Person) obj;//向下转型
}else{
retuen false;
}
if(this.age==p.age&&this.name.equals(p.name)&&this.sex==p.sex&&this.pet==p.pet){
return true;
}else{
return false;
}
}
- == 和equals()的区别( 高频考点)
== 其本质是判断两个引用所指向内存地址是否相同
常用作基本数据类型的判断(String比较特殊,特殊情况下也可以使用)
.equals()常用判断两个对象的值是否相同
但是前提是需要重写该方法
- hashCode()
内存地址的哈希值,在一定程度上反应了存储数据的内存地址,
Person p=new Person("李四", '男', 18);
Person p1=new Person("李四", '男', 18);
//凡是new出来的都是新的对象,都会开辟新的内存,无论他们有多么相似
System.out.println(p.hashCode());//366712642
System.out.println(p1.hashCode());//1829164700
System.out.println(p==p1);//false
Person p2=p;
System.out.println(p.hashCode());//366712642
System.out.println(p2.hashCode());//366712642
System.out.println(p==p2);//true
- toString()
将对象以字符串的形式返回出去
重写toString()
@Override
public String toString() {
return "姓名:" + name + ", 性别:" + sex + ", 年龄:" + age + ", 宠物:" + pet;
}
final关键字
最终的,最后的
修饰类,修饰属性,修饰方法
- 修饰类
public final class 类名{}
表示该类不能被继承
public final class String{}
- 修饰属性
final修饰属性时,当属性赋值过一次后,不能再次赋值
final int age;
Person p=new Person("李四", '男', 18);
System.out.println(p.age);
//p.age=19;//报错
- 修饰形参时
当final关键字修饰形参时,形参的内存地址不能改变,内容可以改变
(在安卓中非常常见)
public void add(final int a,int b,final Dog d){
// a=5;//报错
b=5;
d.name="小黄";
// d=new Dog();//报错
}
- 注意
public static final 变量类型 变量=值;
上述这种写法称之为常量
public static final double PI = 3.14159265358979323846;
public static final double E = 2.7182818284590452354;
- 修饰方法时
表示该方法可以被继承,但是不能被重写
public [static] final 返回值 方法名(参数列表){}
6.抽象 接口 内部类 常用类 包装类 String类 异常
6.1抽象
在生活中一些抽象和定义事物本质概念的,我们适合用抽象去描述,他揭示了is a XXX
比如 动物类,形状类,菜类,水果类
6.1.1抽象类
语法:
public abstract class Animal{}
特点:
抽象类的作用就是定义事物本质,让子类去继承的,可以实现多态
1.能设计成抽象类的类都是用来定义事物本质,其属性和方法都是定义是XXX的条件(平时就思考当方法不好具体实现时,就将该类设计成抽象类)
2.抽象类不能new出对象,可以有构造方法
3.抽象类可以有抽象方法,也可以有普通方法
4.抽象类关注点在属性
6.1.2抽象方法
语法:
普通方法:[权限修饰符] [static] 返回值 方法名(参数列表){方法体}
抽象方法:[权限修饰符] [static] abstract 返回值 方法名(参数列表);
public abstract void eat() ;
特点:
1.没有方法体
2.抽象类被非抽象类继承时,必须要重写
实例:
public abstract class Animal {
String name;//姓名
int age;//年龄
//吃饭
public abstract void eat();
//自我介绍的方法
public void showName(){
System.out.println("姓名:"+this.name+",年龄:"+this.age);
}
}
public class Cat extends Animal{
//eat方法必须要被重写
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
}
6.1.3static,final,abstract总结
| 修饰符 | 修饰对象 | 规则 |
|---|---|---|
| static | 属性 | 表示类公共的属性,使用类来调用,推荐使用类调用(类名.属性名)一般作为计数器使用 |
| 方法 | 表示类公共的方法,在静态方法中只能直接调用静态变量或静态方法,不能使用this,super,如果要想调用非静态方法和属性,需要创建对象。 | |
| 代码块 | 静态代码块,程序一加载静态代码块就运行,而且只运行一次 | |
| final | 属性、局部变量 | 常量,final int i=12;或者final int i;i=12;只能赋值一次。 |
| 方法 | 该方法不能被子类重写,可以被继承。 | |
| 类 | 该类不能被继承。 | |
| abstract | 方法 | 抽象方法,该方法只有方法声明,没有方法体 |
| static方法、final方法不能是抽象的 | ||
| 有该方法的类称为抽象类,类名前必须加abstract | ||
| 类 | 抽象类不能被实例化 | |
| 抽象类可以指向子类对象的引用 | ||
| 抽象类可以有普通方法,可以被子类继承 | ||
| 抽象方法必须在子类中实现,除非子类也是抽象类 |
面试考点:
1.abstract和final能同时修饰一个类吗?
public abstract final class AA{}
不能:抽象类设计出来是一定要被继承,final不能被继承
2.final和static能同时修饰方法吗?
public static final void aaa(){}
可以
3.final和abstract可以同时修饰方法吗?
public abstract final void aaa(){}
不能:
4.static和abstract可以同时修饰一个方法吗?
public static abstract void aaa();
可以但没必要,static 是用类名直接调用,abstract是没有方法体
直接调用一个没有方法体的方法,没有任何意思
5,static和final能同时修饰属性吗?
public static finl int a=1;
可以,是常量
6.2接口
是一种非常特殊的抽象类,他关注的在于抽取事物本质外共有的功能,通常用于描述has a XXX
6.2.1接口语法
public interface 接口名{
//属性默认为常量, 无论你有没有加public static final。
public static final 数据类型 变量名=值;
//方法默认都是抽象方法,无论你有没有加public abstract
public abstract 返回类型 方法名(参数列表);
}
//实现接口
public class 类名 implements 接口名{}
6.2.2接口特点
1.java中的继承都是单继承,接口是多实现
public class Animal extends Object implements Aa,Bb,Cc{}
2.接口和接口之间没有实现,但是可以多继承
public interface Aa extends Bb,Cc,Dd{};
3.接口中的属性默认为常量,方法默认都是抽象方法
6.2.3接口和抽象类的区别
1.抽象类可以有抽象方法,也可以有普通方法,但是接口中都是抽象方法
2.抽象类可以有成员变量,但是接口中都是常量
3.抽象类是揭示事物本质的概念,用于描述 is a XXX
4.在于抽取事物本质外共有的功能,通常用于描述has a XXX
- 注意:抽象类和接口都可以实现多态,也适用于向上向下转型
案例讲解
实现一个盼盼防盗门,其本质是门,拥有防盗的功能
package demo3;
public abstract class Door {
int high;//高度
int width;//宽度
String color;//颜色
public Door() {
super();
}
//开门
public void openDoor(){
System.out.println("门开了");
}
//关门
public void closeDoor(){
System.out.println("锁上了门");
}
}
public interface Alarm {
//防盗报警
void Alarm();
}
public class PanPanDoor extends Door implements Alarm {
@Override
public void Alarm() {
System.out.println("报警....");
}
}
public class Demo {
public static void main(String[] args) {
// 向上转型,丢失了子类的Alarm方法
// Door p=new PanPanDoor();
// p.Alarm();
PanPanDoor p=new PanPanDoor();
p.Alarm();
p.closeDoor();
p.openDoor();
}
}
6.2.4面向接口编程
接口中的方法都是抽象方法,抽象方法对于一个调用者来说是很友好的,所以一般情况接口是用来定义规范的,这就是我们的面向接口编程
需求:张三和李四两个人去开发一个模块
张三去负责主题的业务逻辑
李四去负责具体方法实现
假设:业务需求如下
注册功能的具体实现
用户输入用户名和密码,手机号,家庭地址,邮箱号,身份证号
张三服务主体的业务逻辑
张三拿到了上述的数据后
1.首先他要确定手机号输入的对不对
2.邮箱是不是规范的邮箱
3.身份证格式对不对
4.要去我们家系统中查询下这个手机号有没有注册过
5.再去查询邮箱有没有被绑定过
李四负责上述方法的具体实现
问题是:张三所有业务逻辑实现是基于李四的方法,如果不知道方法,则张三的工作无法开展
那么必然会出现一个人闲着等另一个写完,如果李四有任何情况(请假),张三的工作就无法开展
这样不利于我们后期工作的追责
所以在一开始任务划分之后,张三和李四利用接口去定义规范,接口中都是抽象方法,对于张三来说,调用已经没有任何问题了,对于李四来说,已经规范李四的工作,李四只需要写方法的具体实现即可
使用面向接口编程之后,是程序和程序之间实现了解耦
接口:xxx
接口实现类:xxxImpl
6.3内部类
java中允许类的内部单独定义的一个类,那么每一个内部类去实现和继承其它的类与接口,对外部类不造成任何的影响
- 好处
1.在内部类中可以访问外部类私有化变量
2.每一个内部类去实现和继承其它的类与接口,对外部类不造成任何的影响
- 语法
class Out{
//外部类的变量
//外部类的方法
class In{
//内部类的变量
//内部类的方法
}
}
- 注意
1.java运行的最基本单位是类,c运行的基本单位是函数
2.每一个内部类都会编译生成单独的字节码文件(Out$In.class)
6.3.1成员内部类
内部类像外部类的成员属性和成员方法一样,内部类需要依赖于外部类对象
- 语法
class Out{
//外部类的变量
//外部类的方法
class In{
//内部类的变量
//内部类的方法
}
}
- 注意
1.成员内部类中不能定义静态的属性和方法
2.成员内部类如何在方法中访问外部类的成员变量
a.当成员内部类的属性名和外部类的成员属性名没有重名时,直接调用
b:当成员内部类的属性名和外部类的成员属性名重名时
调用成员内部类的成员变量使用this.a
调用外部类的成员变量使用Out.this.a
3.如何得到成员内部类的对象
外部类名.内部类名 变量名=外部类对象.内部类对象
Out.In in = new Out().new In();
成员内部类编译生成的字节码文件是Out$In.class
实例
public class Out {
//外部类的成员变量
int a=10;
//外部类的成员方法
public void fun() {
System.out.println("外部类的成员方法"+a);
}
//外部类的成员内部类
class In {
//内部类的成员变量
int a=20;
int b;
//static int a;//如果允许?Out.In.a?
//内部类的成员方法
public void fun() {
int a=15;
System.out.println("内部类的局部变量"+a);
System.out.println("内部类的成员变量"+this.a);
System.out.println("外部类的成员变量:"+Out.this.a);
System.out.println("内部类的成员方法"+b);
}
}
}
public class Test {
public static void main(String[] args) {
//如何去调用内部类的成员方法?
//1.得到外部类的对象
/*Out out=new Out();
//2.得到内部类的内部对象
Out.In in=out.new In();*/
//上述两步合并一下
Out.In in=new Out().new In();
in.fun();
}
}
打印结果:
内部类的局部变量15
内部类的成员变量20
外部类的成员变量:10
内部类的成员方法0
6.3.2静态内部类
使用static修饰的内部类,自动提升为普通类,相当于一个独立的类,和外部类级别相同,不依赖于外部类对象
- 语法
class Out{
//外部类的变量
//外部类的方法
static class In{
//内部类的变量
//内部类的方法
}
}
- 注意
1.静态内部类可以去定义静态属性和方法
2.如何得到静态内部类对象
Out.In in=new In();
3.单独使用静态内部类时,需要导包,格式是
import demo1.Out.In;
- 案例
public class Out {
//外部类的成员变量
int a=10;
//外部类的成员方法
public void fun() {
System.out.println("外部类的成员方法"+a);
}
//外部类的静态内部类
static class In {
//内部类的成员变量
static int c=15;
int b;
//static int a;//如果允许?Out.In.a?
//内部类的成员方法
public void fun() {
System.out.println("静态内部类的成员方法"+b);
}
}
}
public class Test {
public static void main(String[] args) {
//静态内部类不依赖于外部类
Out.In in=new In();
in.fun();
System.out.println("打印静态内部类的静态属性"+In.c);
}
}
6.3.3 局部内部类
在方法内声明局部内部类,它的使用范围不能脱离方法
- 语法
class Out{
//外部类的变量
public void 方法名(){
class In{
//内部类的变量
//内部类的方法
}
}
}
- 注意
1.局部内部类的使用离不开方法,所有要想得到局部内部类对象必须在方法内调用
2.局部内部类中如果想使用方法内的局部变量,那么该变量必须为final,形参也适用
在JDK1.8之后,默认在形参和局部变量之前添加final关键
- 案例
public class Out {
//外部类的成员变量
int a=10;
//外部类的成员方法
public void fun( int d) {
final int b=5;//局部变量
System.out.println("外部类的成员方法");
class In{
int c=15;
public void fun(){
//d=15;//报错,因为局部变量和形参前默认添加final关键字,不能二次赋值
//b=15;
System.out.println(b);
System.out.println("局部内部类的成员方法");
}
}
//访问局部内部类对象必须要在方法内访问
In in=new In();
in.fun();
}
}
public class Test {
public static void main(String[] args) {
Out out=new Out();
out.fun(5);
}
}
6.3.4 匿名内部类(重点)
对于一些不能new出来的类和接口,其new出来的就叫匿名内部类,安卓和中间件大量使用
- 语法
public interface Aa{
//抽象方法
}
Aa aa=new Aa(){
//抽象方法的具体实现
};
- 注意
1.匿名内部类的操作对象是接口和抽象类
2.接口和抽象类中的抽象方法必须在匿名内部类中具体实现;
3.如果在方法内部使用匿名内部类,如果匿名内部类中使用到了方法的形参和局部变量,那么该形参和局部变量必须要被final所修饰
好处:
少写一个继承或者实现该抽象类和接口的普通子类
案例
public interface AddDemo {
int add(int a,int b);
}
public class Test {
//如果在方法内部使用匿名内部类,如果匿名内部类中使用到了方法的形参和局部变量,那么该形参和局部变量必须要被final所修饰
public int addTest(final int a, int b) {
final int f = 15;
//匿名内部类时由接口和抽象类new出来的
AddDemo addDemo = new AddDemo() {
@Override
public int add(int a1, int b1) {
int c = a1 + b1 + f + a;
return c;
}
};
int c = addDemo.add(a, b);
return c;
/*
上述也可以写成
int c =new AddDemo() {
@Override
public int add(int a1, int b1) {
int c = a1 + b1 + f + a;
return c;
}
}.add(a, b);
return c;
*/
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.addTest(15, 20));
}
}
总结
1.内部类:
a,成员内部类依赖于外部类对象
b,静态内部类是一个单独类,和普通类同级别
c.局部内部类,只能在方法内部调用
d.匿名内部类其实就是new接口和抽象类的一个过程
6.4常用类
6.4.1 Date类(掌握)
Date表示特定的瞬间,精确到毫秒。Date类中的大部分方法都已经被Calendar类中的方法所取代。
Date类中的构造方法:
| 方法名 | 描述 |
|---|---|
| Date() | 分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。 |
| Date(long date) | 分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。 |
Date类中的成员方法:
1.判断两个日期对象的前后
Date date1 = new Date();//获取当前系统时间
Date date2 = new Date(10000);//获取从标准基准时间起10000毫秒的时间点
boolean boo1 = date1.after(date2);//若date1在date2之后,则返回true,否则返回false
boolean boo2 = date1.before(date2);//若date1在date2之前,则返回true,否则返回false
2.比较两个日期对象
/*
*int compareTo() 比较两个时间
* -1:方法的调用者在实参之前
* 0:相同的时间
* 1:方法的调用者在实参之前
*/
int compareTo = date2.compareTo(date);//-1
System.out.println(compareTo);
int compareTo2 = date.compareTo(date2);//1
System.out.println(compareTo2);
System.out.println(date.compareTo(date));
3.获取时间(获取距离历元过去的毫秒)
Date date = new Date();
long l = date.getTime();
4.修改时间
Date date = new Date();
date.setTime(1000);
6.4.2 Calendar类(了解)
Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR
等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。
Calendar类时抽象类不能创建对象,可以通过Calendar类中的静态getInstance方法获取对象(一个费抽象子类对象).
Calendar类中的常用静态常量属性:
| 属性 | 描述(这些都是特定与日历的值) |
|---|---|
| public static final int YEAR | 指示年的字段数字 |
| public static final int MONTH | 指示月份的字段数字(0-11) |
| public static final int DATE | 指示一个月中的某天 |
| public static final int DAY_OF_MONTH | 指示一个月中的某天 |
| public static final int DAY_OF_WEEK | 指示一周中的某天 |
| public static final int DAY_OF_WEEK_IN_MONTH | 指示当月中的第几个星期 |
| public static final int DAY_OF_YEAR | 指示一年中的某天 |
Calendar类中常用的成员方法:
1.获取一个Calendar对象:
Calendar c = Calendar.getInstance();//getInstance方法是一个静态的方法,直接通过类名调用
System.out.println(c);
2.获取某个日历对象的指定属性的值:
/*
get(int field) 参数就是指定的属性,可以使用静态常量属性名代替int值
*/
//注意:获取日期属性,不能直接用c.DATE,DATE属性时静态常量,所有Calendar类对象都共享并相同的值
Calendar c = Calendar.getInstance();
System.out.println(c.get(Calendar.DATE));//获取c对象所表示日历的日期属性值
3.修改某个日历对象的指定属性值:
/*
set(int field, int value) 第一个参数表示属性,第二个参数表示修改的值
*/
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2017);
4.获取某个属性所表示的最大、最小值
/*
getMaximum(int field) 获取属性的最大值
getMinimum(int field) 获取属性的最小值
*/
Calendar c = Calendar.getInstance();
int max = c.getMaximum(Calendar.DATE);//获取日期的最大值,无论c表示几月份,max的值都是31
int min = c.getMinimum(Calendar.DATE);//获取日期的最小值,无论c表示几月份,min的值都是1
5.获取指定Calendar对象的指定属性的最大、最小值
/*
getActualMaximum(int field) 获取指定日历的指定属性的最大值
getActualMinimum(int field) 获取指定日历的指定属性的最小值
*/
Calendar c = Calendar.getInstance();
int max = c.getActualMaximum(Calendar.DATE);
//若c表示的1月,则max值为31;若c表示的4月,则max值为30;
int min = c.getActualMimimum(Calendar.DATE);//无论c表示几月份,min值都是1
6.4.3 SimpleDateFormat类(重点掌握)
SimpleDateFormat是一个以与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。
通过SimpleDateFormat类将字符串和日期相互转换时,需要使用一些时间模式字母,常用的时间模式字母:
| 字母 | 日期或时间元素 | 示例 |
|---|---|---|
| y | 年 | 1996;96 |
| M | 年中的月份 | July;Jul;07 |
| w | 年中的周数 | 27 |
| D | 年中的天数 | 189 |
| d | 月份中的天数 | 10 |
| a | Am/pm 标记 | PM |
| H | 一天中的小时数(0-23) | 0 |
| h | am/pm 中的小时数(1-12) | 12 |
| m | 小时中的分钟数 | 30 |
| s | 分钟中的秒数 | 55 |
| S | 毫秒数 1秒=1000毫秒 =1000000微秒=1000000000毫微秒(纳秒) | 978 |
1.格式化日期:
/*
format(Date date) 将date对象格式化成指定格式的日期字符串
*/
String format = "yyyy-MM-dd a hh:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(format);
Date date = new Date();
String dateStr = sdf.format(date);
2.解析日期:
/*
parse(String str) 将str对象解析成一个日期对象
*/
String dateStr = "2017-12-01 上午 10:10:10";
String format = "yyyy-MM-dd a hh:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(format);
Date date = sdf.parse(dateStr);
6.4.4 Math类(掌握)
Math类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。
Math类中的静态常量属性
| 属性 | 描述 |
|---|---|
| public static final E | 比任何其他值都更接近 e(即自然对数的底数)的double值。 |
| public static final PI | 比任何其他值都更接近 pi(即圆的周长与直径之比)的double值。 |
Math类中常用的成员方法:
1.绝对值
int a = -5;
System.out.println(Math.abs(a));//输出的结果是5
2.最大、最小值
int a = 5;
int b = 10;
System.out.println(Math.max(a,b));//输出结果为10
System.out.println(Math.min(a,b));//输出结果为5
3.幂运算
int a = 3;
int b = 4;
System.out.println(Math.pow(a,b));//结果为3*3*3*3的结果
4.平方根、立方根
int a = 8;
System.out.println(Math.sqrt(a));//结果为8的正平方跟
System.out.println(Math.cbrt(a));//结果为8的立方根
5.四舍五入
double a = 3.6;
System.out.println(Math.round(a));//结果为4
6.随机数
//产生一个3~9之间的随机数
int a = (int)(Math.random()*(9-3+1)+3);
7 .ceil floor
System.out.println(Math.ceil(3.2)); //大于或等于这个数字的最小正数
System.out.println(Math.floor(2.5));//小于或等于这个数字的最大正数
- 注意(笔试必考)
取整 round,ceil floor
round 四舍五入 原理就是+0.5,向下取整
ceil 天花板 向上取整 在坐标轴上理解为向右取整
floor 地板 向下取整 在坐标轴上理解为向左取整
double a=11.5;
double b=-11.5;
System.out.println(Math.round(a));//12
System.out.println(Math.round(b));//-11
6.4.5 Random类(了解)
此类的实例用于生成伪随机数流。此类使用 48 位的种子,使用线性同余公式 (linear congruential form) 对其进行了修改所得。
如果用相同的种子创建两个Random实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列。
Random类中的构造方法:
| 方法名 | 描述 |
|---|---|
| Random() | 创建一个新的随机数生成器。此构造方法将随机数生成器的种子设置为某个值,该值与此构造方法的所有其他调用所用的值完全不同。 |
| Random(long seed) | 使用单个 long 种子创建一个新的随机数生成器。该种子是伪随机数生成器的内部状态的初始值. |
注意:若long种子确定,则在不同程序中,相同次数产生的随机数是相同的。
Random类中的常用方法:
1.产生随机数
Random random = new Random(10);//以10为种子,使用线性同余公式产生伪随机数
int i1 = random.nextInt();//产生一个随机整数
int i2 = random.nextInt(10);//产生一个10以内的随机整数
double d = random.nextDouble();//产生一个随机double值
boolean b = random.nextBoolean();//产生一个随机boolean值
2.修改种子
Random random = new Random(10);
random.setSeed(20);//将随机数种子设置为20
6.4.6 System类(了解)
System类包含一些有用的类字段和方法。它不能被实例化。
System类中的常见属性:
属性 描述 public static fianl PrintStream err 标准错误输出流 public static final InputStream in 标准输入流 public static final PrintStream out 标准输出流
System类中的常见方法:
1.获取系统时间
long time1 = System.currentTimeMillis();//获取当前时间,毫秒数
long time2 = System.nanoTime();//获取当前时间,毫微秒
2.强制退出虚拟机
System.exit();//强制退出当前正在执行的虚拟机0 非0
3.垃圾回收处理机制:
gc(): 运行垃圾回收器。
举例说明
package day14; public class Person { private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override protected void finalize() throws Throwable { System.out.println("垃圾回收器回收了...."+name); } }public static void main(String[] args) { //垃圾回收,相当于匿名对象 Person person=new Person("张三1", 20); new Person("张三2", 20); new Person("张三3", 20); //回收下垃圾 System.gc(); new Person("张三4", 20); new Person("张三5", 20); new Person("张三6", 20); }
6.4.7 BigDecimal类(重点掌握)
对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数的操作。
BigDecimal b1=new BigDecimal("500.02"); BigDecimal b2=new BigDecimal("400.23"); System.out.println(b1.multiply(b2));//200123.0046 System.out.println(b1.add(b2)); System.out.println(b1.subtract(b2)); BigDecimal b3=b1.divide(b2,2); System.out.println(b3.toString());
6.5包装类
八个基本数据类型:byte short int long float double char boolean
思考:
int a=1;
问题:
//基本数据类型做为属性声明,他会有默认值,我们无法确定这个默认值的含义
//基本数据类型作为变量使用时,不符合面向对象的操作习惯
//如何让1变成对象,如果是对象,我们就可以调用相关行为
//想要有对象,首先必须要有类
6.5.1基本数据类型对应的包装类
java给我们提供这样的一个类,称之为基本数据类型的包装类
byte short int long float double char boolean
Byte Short Integer Long Folat Double Character Boolean
1.2拆装箱
- 装箱
将基本数据类型的变量变成引用数据类型
int a=1;
//虽然i的值也是1,但是i指向了一个新的对象,拥有属性和行为
Integer i=a;//Integer.valueOf(a);
//合并起来
Integer i1=1;//自动装箱
- 好处
1.将数值当成对象去研究,有很多的属性和行为去帮我们完成一些事情
2.变量声明时,使用包装类,默认值将不会有实际意义
- 拆箱(用的比较少)
将引用数据类型转化为基本数据类型
Double b=new Double(3.14);
double c=b;//b.doubleValue();
//合并起来
double c1=new Double(3.14);//自动拆箱
- 案例
Integer i=new Integer(1);
Integer i1=new Integer(1);
int i2=1;
int i3=1;
System.out.println(i2==i3);//true
System.out.println(i==i1);//false new出来的内存地址不一样
//基本数据类型和包装类型做比较时,现将包装类型转化为基本数据类,然后做判断
System.out.println(i1==i3);//true
Integer a=1;//Integer.valueOf(1);
Integer a1=1;
System.out.println(a==a1);//true
//在Integer类中,用到了缓存数组,范围在-128~127之间,
//valueOf()则判断要装箱的基本数据类型是不是在该范围内,
//如果是,则使用缓存,如果不是,则重新new对象
Integer a2=150;
Integer a3=150;
System.out.println(a2==a3);//false
源码分析
Integer i=100;
Integer i=Integer.valueOf(100);
public static Integer valueOf(int i) {
//判断你取的值是不是在某一个范围之内(-128~127)
if (i >= IntegerCache.low && i <= IntegerCache.high)
//如果在这个范围内,就将缓存(数组)中的内存地址返回给你
return IntegerCache.cache[i + (-IntegerCache.low)];
//不在范围内的重新new对象
return new Integer(i);
}
//这是给缓存数组赋值的代码
//声明数组的长度为256,-128~127有256个数
cache = new Integer[(high - low) + 1];
int j = low;//-128
//给数组赋值,将-128赋值在第0个值上,-127赋值在第1个下标
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
- 总结
1.包装类:使基本数据类型作为一个对象去研究而设计出来的
2.装箱:将基本数据类型的值进行包装,包装为引用数据类型
Integer i=1;,他属于自动装箱,默认调用valueOf()这个方法
3.拆箱:将包装类型的值拆出来用基本数据类型去接收
int a=new Integer(1);
只有包装类型和基本数据类型做比较的时候回去做
Integer i=new Integer(1);
int a=1;
a==i//false
int i1=i;//拆箱
a==i1;//true
4.对缓存要敏感,-128~127之间,这中间的值,是从缓存中拿值,所以大家是直线同一个内存地址,超出这个范围就是new对象
6.5.2字符串和基本数据之间的转换(深入掌握)
“123”===》123
123===>“123”
- 基本数据类型转换为字符串类型
String.valueOf(xxx)
123===>"123"
3.14===>"3.14"
true==>"true"
String.valueOf(xxx)
int a=123;
System.out.println(a+1);//124
String a1=String.valueOf(a);
System.out.println(a1+1);//1231
- 字符串转换为基本数据类型
XXX.parseXXX(str)
"123"===>123
"true"===>true
"3.14"===>3.14
String a="123";
System.out.println(a+1);//1231
int a1=Integer.parseInt(a);
System.out.println(a1+1);//124
String b="3.14";
double b1=Double.parseDouble(b);
System.out.println(b1+1);//4.14
注意:没有Character.parseCharacter(str)这样的方法
字符串转换为字符
"a"===>'a'
String a="a";
char c=a.charAt(0);
System.out.println(c+1);//98
- 常用的Integer方法(转二进制、八进制、十六进制)
int a=97;
System.out.println(a+"的二进制是"+Integer.toBinaryString(a));
System.out.println(a+"的八进制是"+Integer.toOctalString(a));
System.out.println(a+"的十六进制是"+Integer.toHexString(a));
- 了解整数类型的最值
MAX_VALUE MIN_VALUE两个常量表示的是最值
总结:
1,基本数据类型转换成字符串
String.valueOf(); 或者直接 数字+“ ”
2.字符串转换为基本数据类型
XXX.parseXXX() xxx.valueOf();
6.6String类
java程序中所有的字符串都是String类的具体实例,String类提供的都是值不能修改的字符串
String类构造对象分为两种方式
- 静态式
String a="abc";
- 动态式
String a=new String("abc");
- 注意
String a="abc";
String a1="abc";
String b1=new String("abc");
String b=new String("abc");
System.out.println(b==b1);//false
System.out.println(b.equals(b1));//true
//使用静态的方式初始化字符串,
//会先在堆内存中的字符串常量池中查找有没有该值出现过,
//如果有,则直接指向该值
//如果没有,则重新在字符串常量池中创建对象
System.out.println(a==a1);//true
6.6.1String类中的构造方法
常用构造方法
| 方法名 | 描述 |
|---|---|
| String() | 创建出一个字符串对象,此字符串中没有任何字符,空字符串 |
| String(String str) | 初始化一个新创建的String对象,使其表示一个与参数相同的字符序列 |
| String(char[] cs) | 通过字符数组参数构建一个字符串 |
String(char[] cs, int offset, int count) | 在字符数组中从第offset位置开始向后截取count个字符组成字符串 |
| String(byte[] bs) | 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。 |
String(byte[] bs,int offset, int count) | 通过使用平台的默认字符集解码指定的 byte 数组,从第offset位置开始向后截取count个字节组成字符串 |
String(int[] bs,int offset, int count) | 通过使用平台的默认字符集解码指定的 int 数组,构造一个新的 String |
| String(byte[] bytes, String charsetname) | 通过使用指定的charset解码指定的 byte 数组,构造一个新的 String |
char[] cs={'今','天','好','闷'};
String a=new String(cs);
System.out.println(a);//今天好闷
String b=new String(cs, 1, 2);
System.out.println(b);//天好
byte[] bs={97,98,99,100,101};
String c=new String(bs);
System.out.println(c);//abcde
String d=new String(bs,2,3);
System.out.println(d);//cde
int[] is={97,98,99,100,101};
String e=new String(is,2,3);
System.out.println(e);//cde
//解决乱码
String f=new String("今天有点热");
byte[] bb=f.getBytes();//将字符串重新转为字节
System.out.println(Arrays.toString(bb));
String g=null;
try {
g= new String(bb,"utf-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println(g);
6.6.2String类中常用的成员方法
1 int length()
获取字符串的长度
不加()是数组中的属性 int []b = {12,34,56,78,910}; i < b.length
数组中.length是属性,字符串中.length()是方法
String a="abcdef";
int length=a.length();
System.out.println(length);
2 int indexOf(可变参数);
获取某个字符或者字符串在原字符串中第一次出现的位置
注意,当查询的值在字符串中不存在的话,则返回-1
1.int indexOf(char c)// 返回c字符在字符串第一次出现的下标
2.int indexOf(String c)// 返回字符串c在字符串第一次出现的下标
3.int indexOf(String c,int fromIndex)// 返回字符串c在字符串在formIndex(包含)后第一次出现的下标
4.int indexOf(char c,int fromIndex)// 返回字符c在字符串在formIndex(包含)后第一次出现的下标
String a="abcdeabcfabc";
//int indexOf(char c)// 返回c字符在字符串第一次出现的下标
char c='c';
int i=a.indexOf(c);
System.out.println(i);//2
//int indexOf(String c)// 返回字符串c在字符串第一次出现的下标
int i1=a.indexOf("abc");
System.out.println(i1);//0
//int indexOf(String c,int fromIndex)// 返回字符串c在字符串在formIndex(包含)后第一次出现的下标
int i2=a.indexOf("abc",6);
System.out.println(i2);//9
//int indexOf(char c,int fromIndex)// 返回字符c在字符串在formIndex(包含)后第一次出现的下标
自己试
3 int lastIndexOf(可变参数)
获取某个字符或者字符串在原字符串中最后一次出现的位置:
注意,当查询的值在字符串中不存在的话,则返回-1
1.int lastIndexOf(char c);//判断字符c在字符串中最后一次出现的下标
2.int lastIndexOf(String str);//判断字符串str在字符串中最后一次出现的下标
3.int lastIndexOf(char ch, int fromIndex);//判断字符c在字符串从第fromIndex(包含)之前中最后一次出现的下标
4.int lastIndexOf(String str, int fromIndex);//判断字符串str在字符串从第fromIndex(包含)之前中最后一次出现的下标
String a="abcdeabcfabc";
//int lastIndexOf(char c);//判断字符c在字符串中最后一次出现的下标
int i=a.lastIndexOf('a');
System.out.println(i);//9
//int lastIndexOf(String str);//判断字符串str在字符串中最后一次出现的下标
System.out.println(a.lastIndexOf("abc"));//9
//int lastIndexOf(char ch, int fromIndex);//判断字符c在字符串从第fromIndex(包含)之前中最后一次出现的下标
System.out.println(a.lastIndexOf('c',7));
//int lastIndexOf(String str, int fromIndex);//判断字符串str在字符串从第fromIndex(包含)之前中最后一次出现的下标
4 char charAt(int index);
获取某个位置上的字符
1.char charAt(int index) 返回字符串中第index位上的字符
String a="abcdeabcfabc";
//char charAt(int index) 返回字符串中第index位上的字符
char c=a.charAt(2);
System.out.println(c=='c');//true
5 boolean contains(String str)
判断字符串中是否包含某个子字符串
contains(CharSequence s)
当且仅当此字符串包含指定的 char 值序列(等同于字符串)时,返回 true
String a="abcdeabcfabc";
boolean b=a.contains("xxx");
System.out.println(b);//false
6 boolean isEmpty()
判断字符串中是否为空(“”)
String a="abcdeabcfabc";
System.out.println(a.isEmpty());//false
String b="";
System.out.println(b.isEmpty());//true
String c=null;
//System.out.println(c.isEmpty());//报错
System.out.println(c==null);//true
System.out.println(b.isEmpty());//判断字符串是否为“”
System.out.println(b==null);//判断字符串是否赋值
7 boolean startWith(String str);
判断字符串是否是以str为开始的
String a="abcdeabcfabc";
boolean b=a.startsWith("abc");
System.out.println(b);//true
8 boolean endWith(String str);
判断字符串是否是以某个后缀开始的
String a="Animal.class";
boolean b=a.endsWith(".java");
System.out.println(b);//false
9 boolean equals(String tr)
判断字符串的内容是否相等
当字符串和常量进行比较
错误写法
str.equals("张三");
str为null的时候会出现空指针异常NullPointerException。
正确写法
"张三".equals(str)
这是源码,自己不要写
//将形参转换为String
String anotherString = (String)anObject;
//调用equqls方法的那个对象的长度
int n = value.length;//4
if (n == anotherString.value.length) {
char v1[] = value;//{'a','b','c','d'}
char v2[] = anotherString.value;//{'a','b','c','d'}
int i = 0;
//把每一个值拿出来作比较,有一个不同就返回false
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
10 boolean equalsIgnoreCase(String str);
忽略大小写相等比较
String a="Animal.class";
boolean b=a.equalsIgnoreCase("ANIMAL.CLASS");
boolean b1="ANIMAL.CLASS".equals(a);
System.out.println(b);//true
System.out.println(b1);//false
11 String replace(String oldStr,String newStr);
将字符串中的oldStr替换成newStr,然后得到一个新字符串
1. String replace(String oldStr,String newStr);
2. String replace(char oldStr,char newStr);
String a="abc12312abc145abc";
String b=a.replace("abc", "fff");
System.out.println(b);//fff12312fff145fff
12 String substring(int beginIndex, int endIndex)
截取
1.String substring(int beginIndex, int endIndex)
将字符串中第beginIndex(包含)到endIndex(不包含)截取成一个新的字符串
2..String substring(int beginIndex)
将字符串中第beginIndex(包含)到最后截取成一个新的字符串
String a="abc12312abc145abc";
String b=a.substring(3, 7);
System.out.println(b);//1231
String c=a.substring(8);
System.out.println(c);//abc145abc
13 String trim();
trim 修剪;修整;切去,割掉,剪下
去除前面和尾部的空格
防止用户输入习惯问题,多了前后的空格
String password="123456 ";//多了个空格
System.out.println("123456".equals(password));//false
String b=password.trim();
System.out.println("123456".equals(b));//true
14 int compareTo(String anotherString)
比较
/*
int compareTo(String anotherString)
按字典顺序比较两个字符串。
int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
*/
/*
如果按字典顺序此 String 对象位于参数字符串之前,则比较结果为一个负整数。
如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。
如果这两个字符串相等,则结果为 0
*/
int num1 = "abc".compareTo("def");
System.out.println(num1);//-3
int num2 = "def".compareTo("abc");
System.out.println(num2);//3
int num3 = "abc".compareTo("abc");
System.out.println(num3);
15 String concat(String str)
拼接
/*
String concat(String str)
将指定字符串连接到此字符串的结尾。
*/
//注意:在String类中,但凡返回值是String类型的方法,生成的都是一个新的字符串,跟原来的字符串没有关系
String a="你好";
String b="我好";
String c=a.concat(b);
System.out.println(c);//你好我好
System.out.println(c==a);//false
16 String[ ] split(String str)
拆分
根据str将字符串拆分成字符串数组
String a="123dabd456dccc";
String[] bs=a.split("d");
//split可以根据正则表达式切割
for (int i = 0; i < bs.length; i++) {
System.out.println(bs[i]);
}
17 char[] toCharArray()
将字符串转换为字符数组
String a="abc";
char[] bs=a.toCharArray();
//split可以根据正则表达式切割
for (int i = 0; i < bs.length; i++) {
System.out.println(bs[i]);
}
18 byte[] getBytes();
将字符串根据字符编码方式转换为字节数组
String a="今天";
byte[] bs=a.getBytes();
for (int i = 0; i < bs.length; i++) {
System.out.println(bs[i]);
}
从上述得知:
1.其实String这个类,他的底层就是char[],并且这个数组是final
2.String类中的各个方法很多都会牵扯到下标这样的概念
6.6.3 StringBuffer类
是提供一个可变字符串的类
//做了十五次new的过程,String类提供的都是值不能修改的字符串
/*String a="1";
for (int i = 0; i < 15; i++) {
a+=i;
System.out.println(a.hashCode());
}*/
//StringBuffer提供的是一个可变字符串的对象
StringBuffer sb=new StringBuffer("0");
for (int i = 0; i < 15; i++) {
sb.append(i);
System.out.println(sb.hashCode());
}
StringBuffer类是String的增强类, 比如: 插入 、追加、替换功能更加强大, 比字符串更加节省内存。
字符串缓冲区:使用缓冲区操作字符串要比直接操作字符串效率高
StringBuffer中的常用构造方法:
| StringBuffer(String str) | 构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。 |
|---|---|
| StringBuffer() | 构造一个其中不带字符的字符串缓冲区,其初始容量为 16 个字符。 |
| StringBuffer(int capacity) | 构造一个不带字符,但具有指定初始容量的字符串缓冲区。 |
常用的成员方法
2.2.1 增加
/*
StringBuffer append(String str)
StringBuffer insert(int offset, String str)
*/
StringBuffer sb2 = sb1.append("hello");
//区别于String类:面盆理论
System.out.println(sb1);
System.out.println(sb2);
System.out.println(sb1 == sb2);//true
sb1.append("java");
System.out.println(sb1);
System.out.println(sb2);
//方法链(了解)
sb1.append("java").append("java").append("java").append("java");
System.out.println(sb1);
System.out.println(sb2);
//插入
sb1.insert(2,"hhhhhhhh");
System.out.println(sb1);
2.2.2 删除
/*
StringBuffer delete(int start, int end)
StringBuffer deleteCharAt(int index)
*/
//删除指定区间的字符串(包含start 不包含end)
sb1.delete(2,3);
System.out.println(sb1);
//删除指定位置上的字符
sb1.deleteCharAt(0);
System.out.println(sb1);
2.2.3 修改
/*
StringBuffer replace(int start, int end, String str)
void setCharAt(int index, char ch)
*/
//替换指定区间的字符串
sb1.replace(2,5,"nnnnnn");
System.out.println(sb1);
//替换指定位置上的字符
sb1.setCharAt(0,'x');
System.out.println(sb1);
2.2.4 获取
//和String类中的用法相同
/*
indexOf
lastIndexOf
charAt
length
substring
*/
2.2.5 反转
// StringBuffer reverse()
StringBuffer sb3 = new StringBuffer("my name is zhansan");
sb3.reverse();
System.out.println(sb3);//
6.6.4 StringBuilder类
StringBuilder类也是字符串缓冲区,类中的方法与StringBuffer类中的方法使用方法一样,区别在于StringBuilder类中的方法都是线程不安全的,而StringBuffer类中的方法都是线程安全的
String,StringBuffer,StringBuilder的区别
String类提供的是一个值不能发生更改的字符串对象
StringBuffer,StringBuilder提供的是一个字符串可以修改的对象
StringBuffer是线程安全
StringBuilder是线程不安全的
对于使用者来说:
1.我们用的最多的还是String
2.特殊的用法:如果一个字符串经常发生修改,推荐使用StringBuffer
6.7枚举
Java中引用类型:数组、类、接口、枚举、注解
6.7.1定义
枚举是一个引用类型,枚举就是一个规定了取值范围的变量类型。
字典值
枚举变量不能使用其他的数据,只能使用枚举中常量赋值。提高程序安全性
声明枚举:
格式:
public enum 枚举名{
//枚举的取值范围
//枚举中可以声明方法
}
//声明一个表示季节的枚举
public enum Season{
SPRING,
SUMMER,
AUTUMN,
WINTER;
public void fun(){}
}
注意:
- 枚举中的成员是此枚举的取值范围;
- 所有的值之间使用逗号分隔,最后一个值后可以加分号也可以不加分号;
- 在枚举中可以声明方法,但是必须在所有的取值之后声明,而且最后一个值后必须使用分号隔开.
6.7.2本质和运用
枚举的本质:其实就是终止类,并继承Enum抽象类。
枚举中的变量,其实就是一个当前类型的静态常量。
枚举结合switch结构使用
public class TestSeasonEnum{
public static void main(String[]args){
//声明一个枚举变量
Season season = Season.SPRING;
//switch小括号中的表达式类型可以是枚举类型
switch(season){
//每个case后的常量直接写枚举的取值范围
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case AUTUMN:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
}
- 常见的枚举
public enum JiJie {
SPRING("春天"),
SUMMER("夏天");
private String desc;//描述
private JiJie(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
private JiJie() {
}
}
/**
main
*/
public class TestJiJie {
public static void main(String[] args) {
JiJie a = JiJie.SPRING;
System.err.println(a.getDesc());
}
}
6.8异常
6.8.1 异常的概念
生活中面对各种异常,比如堵车、撞车等,通常会怎样处理,根据不同的异常进行相应的处理,而不会就此中断我们的生活。
程序中异常:在程序执行过程中由于设计或设备原因导致的程序中断的异常现象叫做异常。
异常处理:Java编程语言使用异常处理机制为程序提供了异常处理的能力。
异常演示:
public class Test1 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = in.nextInt();
System.out.println(num1/num2);
System.out.println("感谢使用本程序!");
}
}

6.8.2 异常的分类
运行时异常
运行时异常:(RuntimeException)在编译过程不会发现(没有语法错误),但是在执行程序过程中,由于重大的逻辑错误导致的程序中断。
所有的RuntimeException的子类包括RuntimeException都属于运行时异常
常见的运行时异常
NullPointerException 空指针异常(一个对象没有初始化调用方法)
IndexOutOfBoundsException 下标越界异常
ClassCastException 类型转换异常(对象类型转换时)
NumberFormatException 数字格式异常
ArithmeticException 算术异常.
ClassNotFoundException 类加载异常
编译时异常
编译异常或检查异常,在程序设计过程中,编译时就会被发现,但是执行时可能发生也可能不发生的异常,为了程序不报错可以执行,那么这一类异常必须进行相应的处理。
Exception的子类包括Exception,除了RuntimeException之外都属于编译时异常。
Exception类:异常的父类。
Error类:错误,错误比较严重的问题,不属于异常,程序员无法处理。
StackOverflowError 、OutOfMemoryError
由于运行时异常是逻辑错误造成,最终的解决方案,只有提Bug,然后修改。
那么java所提供的异常处理机制,只有两套:自己解决和抛出去别人解决
最终异常处理机制能做的事,只有捕获到异常,并且记录下来,然后跳过去
6.8.3 异常处理
Java的异常处理是通过5个关键字来实现的:
- try:执行可能产生异常的代码
- catch:捕获异常 ,并处理
- finally:无论是否发生异常,代码总能执行
- throw: 手动抛出异常
- throws:声明方法可能要抛出的各种异常
1 try-catch块
使用try-catch块捕获异常,分为三种情况
第一种情况:正常情况
public void method(){
try
// 代码段(此处不会产生异常)
}catch(异常类型ex){
// 对异常进行处理的代码段
}
//代码段
}
第二种情况:出现异常
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
// 代码段
} catch (异常类型 ex) {
// 对异常进行处理的代码段3
}
// 代码段4
}
printStackTrace():此方法用来显示异常信息。
第三种情况:异常类型不匹配
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
// 代码段 3
} catch (异常类型 ex) {
// 对异常进行处理的代码段4
}
// 代码段5
}
2 try-catch-finally
在try-catch块后加入finally块,finally块的主要作用释放资源。
(1)finally块是否发生异常都执行
(2)finally块不执行的唯一情况,退出java虚拟机,System.exit(); 0正常退出,非0非正常退出
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
} catch (异常类型 ex) {
// 对异常进行处理的代码段3
return;
}finally{
// 代码段 4
//做资源关闭
}
}
3 多重catch块(try…catch变形)
引发多种类型的异常
(1)排列catch 语句的顺序:先子类后父类
(2)发生异常时按顺序逐个匹配
(3)只执行第一个与异常类型匹配的catch语句
public void method(){
try {
// 代码段
// 产生异常(异常类型2)
} catch (异常类型1 ex) {
// 对异常进行处理的代码段
} catch (异常类型2 ex) {
// 对异常进行处理的代码段
} catch (异常类型3 ex) {
// 对异常进行处理的代码段
}
// 代码段
}
4 try…finally
try…finally不能捕获异常 ,仅仅用来当发生异常时,用来释放资源。
一般用在底层代码,只释放资源不做异常处理,把异常向上抛出。
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
}finally{
// 代码段 3
}
}
try-catch语句使用场景
只在main方法中才能使用,其他情况不能使用
5 声明异常
如果在一个方法体中抛出了异常,如何通知调用者?
throws关键字:声明异常
使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断。
public static void divide() throws Exception {
//可能出现异常的代码
}
public static void main(String[] args) {
try {
divide();
} catch (Exception e) {
e.printStackTrace();
}
}
或
public static void main(String[] args) throws Exception {
divide();
}
6 抛出异常
除了系统自动抛出异常外,有些问题需要程序员自行抛出异常。
throw关键字:抛出异常
public class Person {
private String name = ""; // 姓名
private int age = 0; // 年龄
private String sex = "男"; // 性别
public void setSex(String sex) throws Exception {
if ("男".equals(sex) || "女".equals(sex))
this.sex = sex;
else {
throw new Exception(“性别必须是\"男\"或者\"女\"!");
}
}
}
6.8.4 自定义异常类
创建自定义异常,通常继承自Exception 或其子类,习惯上包装一下父类的构造方法。
1.过程:自定义非运行时异常
i.定义一个类,继承Exception类或RuntimeException
ii.在此类中定义构造方法,调用父类中的带字符串参数的构造方法(此字符串表示对异常的描述)
iii.使用异常
/**
* 类说明
*/
public class LoginException extends Exception{
public LoginException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
@Override
public String getMessage() {
// TODO Auto-generated method stub
return super.getMessage();
}
}
使用
public void login(String name,String password) throws Exception{
if(password.length()<=5){
throw new LoginException("密码至少要六位");
}
if(!("张三".equals(name)&&"123456".equals(password))){
throw new LoginException("用户名和密码不匹配...");
}
}
测试
public static void main(String[] args) {
String name="张三";
String password="12345";
Demo d=new Demo();
try {
d.login(name, password);
System.out.println("登陆成功....");
} catch (Exception e) {
System.out.println("登录失败");
System.out.println(e.getMessage());
e.printStackTrace();
}
}
- 案例二
/**
* 性别异常
* @author wgy
*
*/
public class SexException extends Exception {
public SexException() {
super();
// TODO Auto-generated constructor stub
}
public SexException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
使用
public void setSex(String sex) throws Exception {
if(sex.equals("男")||sex.equals("女")) {
this.sex = sex;
}else {
//抛出异常
throw new SexException("性别有错误");
}
}
- 注意
1.异常处理机制是来处理程序中合理化出现的问题
2.try-catch 是用来捕获异常并且处理,防止对程序接下来的运行造成影响
3.throws是向外部声明方法中可能会出现该类型异常的具体对象,通常写在方法的后面
4.throw是抛出具体的异常对象
5.在main方法中出现异常,就进行捕获,在方法设计时,一般就往外抛
6.以后在源码中,如果调用源码抛出了异常,那么你也要处理。就看是不是在main中
7.集合List Set Map
7.1集合概念 结构 异同
1.1 集合的概念
集合:就是一个存储数据的容器。
集合与数组一样,也是一个容器,与数组的区别:
1 数组长度固定,集合的长度不固定。
2 数组可以存储基本类型和引用类型,集合中存储的元素类型只能是引用类型。
集合的 fail-fast机制和 fail-safe机制:
fail-fast快速失败机制,一个线程A在用迭代器遍历集合时,另个线程B这时对集合修改会导致A快速失败,抛出ConcurrentModificationException 异常。在java.util中的集合类都是快速失败的。
fail-safe安全失败机制,遍历时不在原集合上,而是先复制一个集合,在拷贝的集合上进行遍历。在java.util.concurrent包下的容器类是安全失败的,建议在并发环境下使用这个包下的集合类。
1.2 集合的框架结构介绍
Collection和Map是两个高度抽象的接口:
- Collection抽象的是集合,包含了集合的基本操作和属性,Collection主要包含List和Set两大分支。
- List是有序的链表,允许存储重复的元素,List的主要实现类有LinkedList, ArrayList, Vector, Stack。
- Set是不允许存在重复元素的集合,Set的主要实现类有HastSet和TreeSet(依赖哈希实现)。
Collection集合的框架结构
Map集合的框架结构
1.3 集合与数组的对比
相同点:
都是数据的容器,在一个数组或集合中可以存储多个数据
不同点:
元素:数组中的元素只能是相同,集合中的元素是任意的(泛型)
数组中可以存储基本类型和引用类型,集合只能存储引用类型
长度(元素个数):
数组是定长的,一旦初始化长度就不可以修改
集合长度可以修改,可以删除元素和添加元素
7.2Collection接口
2.1 Collection中常用的方法
| 方法名 | 描述 |
|---|---|
| add(E e) | 确保此 collection 包含指定的元素(可选操作)。 |
| addAll(Collection<? extends E> c) | 将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 |
| clear() | 移除此 collection 中的所有元素(可选操作)。 |
| contains(Object o) | 如果此 collection 包含指定的元素,则返回true。 |
| containsAll(Collection<?> c) | 如果此 collection 包含指定 collection 中的所有元素,则返回 true。 |
| equals(Object o) | 比较此 collection 与指定对象是否相等。 |
| isEmpty() | 如果此 collection 不包含元素,则返回true。 |
| iterator() | 返回在此 collection 的元素上进行迭代的迭代器。 |
| remove(Object o) | 从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 |
| removeAll(Collection<?> c) | 移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 |
| retainAll(Collection<?> c) | 仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。 |
| size() | 返回此 collection 中的元素数。 |
| toArray() | 返回包含此 collection 中所有元素的数组。 |
实践
//1创建Collection对象(Collection是无序的)
Collection collection=new ArrayList();
//2操作
//2.1添加
collection.add("张三");
collection.add("李四");
collection.add("他二大爷");
collection.add("他老爷");
collection.add("七大妈");
collection.add("张三");
System.out.println("集合的元素个数:"+collection.size());
//2.2删除
//2.2.1删除一个
// collection.remove("张三");
// System.out.println("删除之后的数据个数:"+collection.size());
//2.2.2清空
// collection.clear();
// System.out.println("清空之后:"+collection.size());
//3遍历
//3.1 使用增强for
System.out.println("--------使用增强for遍历----------");
for(Object obj:collection){
System.out.println(obj);
}
//4判断
//4.1判断是否存在
boolean b=collection.contains("王五");
boolean b2=collection.contains("他老爷");
System.out.println(b);
System.out.println(b2);
//4.2判断是否为空
System.out.println(collection.isEmpty());
//5查找
//6其他方法
Collection c1=new ArrayList();
c1.add("中国");
c1.add("朝鲜");
Collection c2=new ArrayList();
c2.add("中国");
c2.add("朝鲜");
System.out.println("c1.equals(c2):"+c1.equals(c2));
System.out.println("c1==c2:"+(c1==c2));
实践2
Collection collection=new ArrayList();
//1添加
collection.add(111);//111自动装箱 new Integer(111)
collection.add(222);
collection.add("111");
collection.add("222");
//2删除
System.out.println("元素的个数:"+collection.size());
System.out.println("删除之前:"+collection);
collection.remove("111");
System.out.println("删除之后:"+collection.size());
System.out.println("删除之后:"+collection);
//3遍历
//3.1增强for循环
System.out.println("增强for.....");
for (Object object : collection) {
System.out.println(object);
}
//3.2使用迭代器
System.out.println("迭代器.....");
7.3 List接口
List集合常用子类:
- ArrayList:底层是数组,线程不安全;
- LinkedList:底层是双向链表,线程不安全;
- Vector:底层是数组,线程安全;
- Stack:底层是数组,继承于Vector,线程安全;
| 方法名 | 描述 |
|---|---|
| add(int index, E element) | 在列表的指定位置插入指定元素(可选操作)。 |
| addAll(int index, Collection<? extends E> c) | 将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)。 |
| containsAll(Collection<?> c) | 如果列表包含指定 collection 的所有元素,则返回true。 |
| get(int index) | 返回列表中指定位置的元素。 |
| indexOf(Object o) | 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 |
| lastIndexOf(Object o) | 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。 |
| listIterator() | 返回此列表元素的列表迭代器(按适当顺序)。 |
| remove(int index) | 移除列表中指定位置的元素(可选操作)。 |
| set(int index, E element) | 用指定元素替换列表中指定位置的元素(可选操作)。(Collection中没有) |
| subList(int fromIndex, int toIndex) | 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。 |
- 注意
//remove(int index) 和 remove(Object o)
//当出现删除数字时,优先删除下标
ArrayList list=new ArrayList();
list.add(1);
//将值增加的到指定位置
list.add(2);
list.remove(1);//将2删除
7.3.1 List接口的存储特点
1 相对有序存储,可以存储相同元素(不排重),可以通过下标访问集合元素,get(i)==arr[i]
2 List接口中可以使用独有的迭代器ListIterator,具有反向遍历的功能
7.3.2 List接口的实现类
7.3.2.1 ArrayList类
ArrayList类是List接口的大小可变数组的实现。实现了所有可选列表操作,
并允许包括null在内的所有元素。
存储特点:
相对有序存储,可以存储相同元素(不排重),可以通过下标访问集合元素,通过数组实现的集合,
扩容时将老数组元素拷贝到新数组中,每次扩容是其容量的1.5倍,操作代价高
存储结构:数组
底层是数组:所以下标是自带属性,长度固定
但是我们集合是长度不固定
采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险;
ArrayList是线程不安全的,所以在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
构造方法
初始化数组,这个数组的容量为0注意:很多面试宝典中说ArrayList的数组初始化容量为10,这是错的当第一次调用add这个方法(放入第一个元素的时候),此时会对数组进行扩容,并且将容量赋值为10,并且将第0个下标赋值为你add的值,扩容代码如下
//数组扩容的代码
private void grow(int minCapacity) {//10
// overflow-conscious code
int oldCapacity = elementData.length;//0
int newCapacity = oldCapacity + (oldCapacity >> 1);//0
if (newCapacity - minCapacity < 0)
//把最小的容量10赋值新容量
newCapacity = minCapacity;
//数组的容量绝对不能超过2的31次方-1
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//对底层的数组扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
当我们持续赋值之后,容量必然超过10,此时怎么扩容呢?
第二次赋值,会将原数组进行copy,然后复制到新的数组,该新的数组容量为15原长度+原长度的一半
//数组第n次扩容的代码(n>1)
private void grow(int minCapacity) {//11
// overflow-conscious code
int oldCapacity = elementData.length;//10
int newCapacity = oldCapacity + (oldCapacity >> 1);//15(原length+length的一半)
if (newCapacity - minCapacity < 0)
//把最小的容量10赋值新容量
newCapacity = minCapacity;
//数组的容量绝对不能超过2的31次方-1
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//对底层的数组扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
基于上述源码的理解:
ArrayList他的底层就是数组,下标是自带属性,所以在做查找和修改的速度会比较快
但是由于数组的增加会牵扯到扩容,删除的时候会对元素进行覆盖,所以ArrayList在做数据的增删时,效率比较低
- 增强型for循环(foreach)
语法
//遍历的数据类型必须为引用数据类型
for(容器中的引用数据类型 变量名:容器){
//变量名就是每一个对象
}
import java.util.ArrayList;
import java.util.List;
/**
* 相对有序存储,
* 可以存储相同元素(不排重),
* 可以通过下标访问集合元素
*/
public class ListDemo {
public static void main(String[] args) {
ArrayList<DVD> list=new ArrayList<DVD>();
list.add(new DVD("CD0001", "满城尽带黄金甲", "烂片"));
list.add(new DVD("CD0002", "哪吒", "动漫"));
list.add(new DVD("CD0003", "战狼", "主流片"));
list.add(new DVD("CD0005", "倩女幽魂", "鬼片"));
list.add(new DVD("CD0006", "2012", "灾难片"));
//小for循环
/*for (int i = 0; i <list.size(); i++) {
System.out.println(list.get(i).toString());
}*/
//增强型for循环,适用于数组,集合
for(DVD d:list){
System.out.println(d.toString());
}
String[] ss={"123","456"};
for(String s:ss){
System.out.println(s);
}
}
}
ArrayList 的一些操作
public class ArrayListDemo {
public static void main(String[] args) {
//创建一个ArrayList集合
ArrayList<String> list = new ArrayList<>();//构造方法中的泛型可以省略
list.add("zhangsan");//向集合中添加元素
list.add("lisi");
list.add("wangwu");
System.out.println(list.isEmpty());//判断list集合是否为空集合
System.out.println(list.size());//查看集合中元素的个数
System.out.println(list.get(1));//获取集合中下标为1的元素
System.out.println(list.set(1,"zhaoliu"));//修改集合中下标为1的元素
System.out.println(list.contains("wangwu"));//查看"wangwu"是否是集合中的元素
list.remove("wangwu");//删除集合中"wangwu"元素
list.remove(1);//删除集合中下标为1的元素
//for循环遍历集合
for(int i=0; i<list.size(); i++){
System.out.println(list.get(i));
}
}
}
案例
package listDemo;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("张三", '男', 18, 92));
list.add(new Student("张三1", '男', 18, 86));
list.add(new Student("张三2", '女', 18, 73));
list.add(new Student("张三3", '女', 18, 92));
//求男女生人数
int countMan=0;
for (Student s:list) {
if('男'==s.getSex()){
countMan++;
}
}
System.out.println("男生人数"+countMan+",女生人数为:"+(list.size()-countMan));
int sumScore=0;//总成绩
for (int i = 0; i < list.size(); i++) {
sumScore+=list.get(i).getScore();
}
System.out.println("平均成绩:"+((double)sumScore/list.size()));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).toString());
}
for (int i = 0; i < list.size(); i++) {
if("张三4".equals(list.get(i).getName())){
list.get(i).setName("王五");
}
}
System.out.println("将张三4的姓名改为王五");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).toString());
}
System.out.println("将张三5的姓名删除");
//如果做删除,不能使用foreach循环
/*for (Student s:list) {
if("张三5".equals(s.getName())){
list.remove(s);
}
}
*/
for (int i = 0; i < list.size(); i++) {
if("张三5".equals(list.get(i).getName())){
list.remove(i);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).toString());
}
}
}
使用对象排序
重写Comparable接口
如果想使用Collections.sort()的方法对集合进行排序
1.首先泛型类型的javaBean必须实现Comparable接口,并且将T改成该Bean类型
2.重写compareTo方法,重设对象之间排序条件,此时用属性进行对比,返回正数,0,负数即可(具体升序降序,看你返回的数)
3.如果对对象数组也想使用Arrays.sort(),同理
使用Collections工具类来进行排序
你使用对象去进行排序,那么该对象知道你的排序规则吗?
需要去告诉我们的对象,需要我们javaBean来实现Compareable接口,并且重写他的抽象方法compareTo
public class Student implements Comparable<Student>{//传的泛型要保持一致
private String name;
private char sex;
private int age;
private int gread;//成绩
public Student(String name, char sex, int age, int gread) {
super();
this.name = name;
this.sex = sex;
this.age = age;
this.gread = gread;
}
public Student() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGread() {
return gread;
}
public void setGread(int gread) {
this.gread = gread;
}
@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + ", age=" + age + ", gread=" + gread + "]";
}
//告诉比较器我们的比较规则
@Override
public int compareTo(Student o) {
//通过返回的正数和负数来表示关系大小
if(this.gread>o.getGread()){
return 1;
}else{
return -1;
}
}
}
package listDemo;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("张三", '男', 18, 92));
list.add(new Student("张三1", '男', 18, 86));
list.add(new Student("张三2", '女', 18, 73));
list.add(new Student("张三3", '女', 18, 92));
for(Student s:list){
System.out.println(s.toString());
}
//根据成绩排序
//冒泡排序
/*for (int i = 0; i < list.size()-1; i++) {
for (int j = 0; j < list.size()-1-i; j++) {
if(list.get(j).getScore()<list.get(j+1).getScore()){
Student s=list.get(j);
list.set(j, list.get(j+1));
list.set(j+1, s);
}
}
}*/
//使用Collections工具类来进行排序
//你使用对象去进行排序,那么该对象知道你的排序规则吗?
//需要去告诉我们的对象,需要我们javaBean来实现Compareable接口,并且重写他的抽象方法compareTo
Collections.sort(list);
for(Student s:list){
System.out.println(s.toString());
}
}
}
7.3.2.2 LinkedList类
基本使用和ArrayList没有任何一点点区别
LinkedList类是List接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括null)。
底层是:Node(结点)
存储特点:
相对有序存储,可以存储相同元素(不排重),可以通过下标访问集合元素,通过链表实现的集合
存储结构:双向链表
/* LinkedList的使用
* 存储结构:链表
*/
public class Demo5 {
public static void main(String[] args) {
//创建集合
LinkedList<String> linkedList=new LinkedList<>();
//1添加元素
linkedList.add("苹果");
linkedList.add("葡萄");
linkedList.add("西瓜");
linkedList.add("芒果");
linkedList.add("桃子");
System.out.println("元素个数:"+linkedList.size());
System.out.println("打印:"+linkedList.toString());
//2删除
// linkedList.remove(0);
// System.out.println("删除之后:"+linkedList.toString());
//3遍历
//3.1for
//3.2foreach
//4判断
System.out.println("isEmpty:"+linkedList.isEmpty());
System.out.println("苹果:"+linkedList.contains("苹果"));
//5查找位置
System.out.println("桃子的位置:"+linkedList.indexOf("桃子"));
}
}
ArrayList和LinkedList的总结
ArrayList存储结构是数组,LinkedLis存储结构是链表。
ArrayList集合适用在对元素查询、遍历操作,不适合插入和删除。
LinkedList集合适用在对元素插入和删除操作,不适合遍历和查找。
99.98%用的都是ArrayList
7.3.2.3 Vector(了解)
Vector类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是Vector的大小可以根据需要增大或缩小,以适应创建 Vector后进行添加或移除项的操作。
public static void main(String[] args) {
//创建集合
Vector<String> vector=new Vector<>();
//1添加元素
vector.add("xxx");
vector.add("yyy");
vector.add("zzz");
System.out.println(vector.toString());
//2删除
// vector.remove(0);
// System.out.println("删除之后:"+vector.toString());
//3遍历
//3.1for
//3.2foreach
//3.3迭代器
//3.4枚举器
Enumeration<String> elements = vector.elements();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
7.3.2.4 Stack(了解)
Stack类表示后进先出(LIFO)的对象栈。是Vector的子类。
堆栈结构先进后出
队列结构是先进先出
public static void main(String[] args) {
//创建集合(先进后出,后进先出)
Stack<String> stack=new Stack<>();
//1入栈
stack.push("abc");
stack.push("hello");
stack.push("你好");
stack.push("他好");
stack.push("大家好");
//2出栈
System.out.println("---------出栈---------");
int size=stack.size();
for(int i=0;i<size;i++) {
String s=stack.pop();
System.out.println(s);
}
}
7.3.2.5 ArrayList与LinkedList,Vector三种实现类存储的比较
1.功能基本相同
2.底层存储结构:ArrayList是数组,LinkedList是链表,Vector是数组
3.Vector是一个古老的集合,从JDK1.0开始就有了,Vector存在一些方法名比较长的方法,xxxElement
4.Vector是线程安全的,效率低,ArrayList是线程不安全的,效率高,推荐使用ArrayList【Collections工具类中有相应的方法可以将ArrayList改为线程安全的】
5.ArrayList查找遍历比较快,LinkedList插入删除比较快
7.4 Iterator迭代器

7.4.1 迭代器的工作原理
迭代器相当于一个游标,最初获取迭代器时,迭代器的位置在所有元素的前面,每迭代一个元素,迭代器向后移动一个位置

7.4.2 迭代器的使用
| 方法名 | 描述 |
|---|---|
| hasNext() | 判断迭代器是否存在下一个元素可以迭代 |
| next() | 迭代器迭代下一个元素 |
//第三种遍历的方法
Iterator<Student> i=list.iterator();
//hasNext,有没有下一个值
while(i.hasNext()){
//i.next()返回的是对象,而且没执行一次,指针就会向下走一次
System.out.println(i.next().toString());
}
7.5 泛型(了解)
是我们java设计的一个趋势
实例泛型
7.5.1 什么是泛型
泛型是用来定义数据类型的规范,他可以规范我的变量的数据类型
7.5.2 泛型的声明
1)泛型可以声明在类中:(泛型类)
注意:
1 <E>:<>表示泛型,E(Element)表示占位符代替一个数据类型 ,其他的名称T(Type) V(Value) K(Key)
2 可以写多个泛型参数,如果写多个中间用逗号隔开
public class 类名<占位符>{
//类体
//泛型可以在类中充当成员变量
//泛型可以再类中充当方法的返回值
//泛型可以在类中充当方法的参数
}
2)泛型可以声明在方法中:(泛型方法)
public static <标识符> void fun(){}
3)泛型可以声明在接口中:(泛型接口)
public interface 接口名<标识符>{
//泛型可以充当接口中方法的返回值
//泛型可以充当接口中方法的参数
}
案例1:
/**
* 老师类
* 泛型类
* T 占位符:表示未知的数据类型
*/
public class Teacher<T>{
String name;
int age;
int workyear;
T t;
public void show() {
System.out.println("姓名:"+name+" 年龄:"+age+" 工作年限:"+workyear+" t:"+t);
}
}
案例2:
/**
* 学生类
*/
public class Student {
String name;
int age;
public void show(String s) {
System.out.println("姓名:"+name+" 年龄:"+age+" "+s);
}
/**
* 泛型方法
* T 占位符 表示一种未知的数据类型
* T type
* E
* K
* V
*/
public <T> void fun(T t) {
System.out.println(t);
}
}
案例3:
/**
* 接口
* 泛型的
*/
public interface MyCollection<T> {
void add(T t);
void remove(T t);
T get();
}
package com.qf.day15_2;
public class MyArray<T> implements MyCollection<T>{
@Override
public void add(T t) {
// TODO Auto-generated method stub
System.out.println("添加了:"+t);
}
@Override
public void remove(T t) {
// TODO Auto-generated method stub
System.out.println("删除了:"+t);
}
@Override
public T get() {
// TODO Auto-generated method stub
return null;
}
}
案例4
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.net.ssl.SSLContext;
public class Demo1 {
public static void main(String[] args) {
//创建泛型对象 元素类型String
Collection<String> coll=new ArrayList<String>();
//1添加
coll.add("苹果");
coll.add("桔子");
coll.add("芒果");
coll.add("西瓜");
System.out.println("元素个数:"+coll.size());
System.out.println(coll);
//2删除
// coll.remove("苹果");
// System.out.println("删除之后");
// System.out.println(coll);
//3遍历
//3.1增强for
System.out.println("---------增强for--------");
for(String s:coll) {
System.out.println(s);
}
System.out.println("---------迭代器--------");
Iterator<String> it=coll.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
7.5.3 说明
1)标识符(占位符):只要是一个合法的标识符即可,一般情况下,只使用一个大写字母表示泛型
例:public class Person<T>{} E(Element) K(key) V(value) T(Type)
2)泛型的类型与声明的类或接口不需要有任何的关系
3)泛型可以在类中充当任何的成员
4)泛型具体类型取决于实例化对象时传入的实际类型
7.5.4 泛型使用时的注意事项
1)泛型不能在类中声明静态属性、常量
final修饰的属性必须在声明的同时初始化,所以泛型不能声明常量。
static修饰的属性是静态属性,先于对象,泛型类型取决于创建对象时传入的实际类型,所以泛型不能声明静态属性
综上所述:不能使用泛型声明静态属性、常量
2)泛型不能在类中初始化数组,但是可以声明数组
初始化数组时需要给元素进行分配空间,但是泛型类型不确定无法分配空间
3)在类中不能使用泛型声明参数个数相同的重载方法
当一个类中有两个泛型时,创建对象时,两个泛型使用相同类型替换,那么重载方法就是相同的方法(同名,参数列表也相同)
4)使用不同实际类型创建出的泛型类对象的引用不可以相互赋值
7.5.5 受限泛型(理解即可)
1)<?>:表示任意类型
2)<? extends T>:表示T类或者T类的子类,必须写的是接口或者抽象类
3)<? super T>:表示T类或者T类的父类
7.5.6 泛型应用在集合上
泛型在集合中应用,表示的是集合中元素的类型
总结
1 集合概念,用来存储一组数据的容器。和数组类似
数组是长度固定的,集合长度可以变化。
数组能存储基本类型和引用类型,集合只能存储引用类型。
2 Collection接口,父接口, add() remove() clear() contains() iterator()
3 Collection有两个子接口 List和Set
4 泛型:本质使用数据类型作为参数传递
4.1 定义泛型类 泛型方法 泛型接口
4.2 使用泛型类创建类对象,指定泛型的实际类型
4.3 泛型限制,<?> 表示任何类型
<? extends T> 表示泛型上限, T类型或T的子类
<? super T> 表示泛型下限,T类型或T的父类
5 Iterator迭代器
hasNext();
next();
remove();
6 List接口
List接口的特点:有序的,可以重复
7 ArrayList实现类
存储结构:数组
适合遍历查找
8 LinkedList实现
存储结构:双向链表
适合做添加,删除
9 Vector向量集合 Stack栈 (先进后出)
7.6 Set接口
7.6.1 Set接口常用方法
| 方法名 | 描述 |
|---|---|
| add(E e) | 确保此 collection 包含指定的元素(可选操作)。 |
| addAll(Collection<? extends E> c) | 将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 |
| clear() | 移除此 collection 中的所有元素(可选操作)。 |
| contains(Object o) | 如果此 collection 包含指定的元素,则返回true。 |
| containsAll(Collection<?> c) | 如果此 collection 包含指定 collection 中的所有元素,则返回 true。 |
| equals(Object o) | 比较此 collection 与指定对象是否相等。 |
| isEmpty() | 如果此 collection 不包含元素,则返回true。 |
| iterator() | 返回在此 collection 的元素上进行迭代的迭代器。 |
| remove(Object o) | 从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 |
| removeAll(Collection<?> c) | 移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 |
| retainAll(Collection<?> c) | 仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。 |
| size() | 返回此 collection 中的元素数。 |
| toArray() | 返回包含此 collection 中所有元素的数组。 |
7.6.2 存储特点
相对无序存储,不可以存储相同的元素(排重),不能通过下标访问
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Set接口
* 特点:无序,不可重复
* @author wgy
*
*/
public class Demo1 {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("你好");
set.add("我好");
set.add("她好");
set.add("大家好");
set.add("你好");//地址相同,值也相同,添加不进去
set.add(new String("你好"));//地址不同,内容相同也添加不进去
System.out.println(set.size());//长度
//set.remove("你好");
//清空set.clear();
System.out.println(set.contains("我好"));
//增强型for
for(String s:set){
System.out.println(s);
}
//迭代器迭代
Iterator<String> i=set.iterator();
while(i.hasNext()){
System.out.println(i.next());
}
//转换成数组
Object[] os=set.toArray();
for (int j = 0; j < os.length; j++) {
if(os[j] instanceof String){
String a=(String) os[j];
System.out.println(a);
}
}
}
}
7.6.3 Set常用实现类
1 HashSet
此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。
Hash:哈希——实际含义散列,就是一种算法,把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。
哈希表:数组加链表,既有数组的优点也有链表的优点。
存储特点:
相对无序存储,不可以存储相同元素(排重),通过哈希表实现的集
HashSet的去重原理(重点)
第一步:先判断你说添加的对象和set集中对象的hashCode()有没有相同的,如果没有,则直接添加进去,如果有相同的hashCode()方法的返回值,则进行第二步
第二步:此时再去将两个相同hashCode方法相同的对象,去执行equals判断,如果返回的是true,那么此时就会判断这两个对象是重复值,那么第二个就不能添加了,如果是false,那就可以添加
所以:
Set<String> set = new HashSet<String>();
String str1 = new String("你好");
set.add(str1);
System.out.println(str1.hashCode()); //652829
String str2 =new String("你好");
set.add(str2);
System.out.println(str2.hashCode()); //652829
System.out.println(str1.equals(str2)); //true
System.out.println(str1 == str2); //false
System.out.println(set.size());//长度
//我的疑问是为什么str1和str2的哈希值是一样的,str1和str2都是new出来的 地址不一样那为什么哈希值一样呢
// 而下面s1和s2的哈希值为什么不一样,那既然String类重写了hashcode方法和equals方法那为什么我们还要重写
//我的发现: str1和str2调用的是String类里重写的hashcode方法,字符串String的hashCode()是根据内容计算的所以哈希值一样。
//而s1和s2调用的是Student继承的Object类的hashcode方法,所以需要在Student中重写hashcode和equals方法
System.out.println("------------");
Set<Student> set1 = new HashSet<>();
Student s1 = new Student("李宁", '男', 18, 60);
Student s2 = new Student("李宁", '男', 18, 60);
如果都是默认的没重写方法
Student s3=new Student("张三3", '男', 18, 90);
Student s4=new Student("张三3", '男', 18, 90);
s3和s4的hashCode方法返回值是不一样的,所以就没必要在判断equals方法,这是hashSet就会判断s3和s4是不同对象,就会把s4添加进去
但是我们不希望s4也能添加进去
所以我们必须要去重写haseCode方法和equals方法
@Override
public int hashCode ( ) {
return this.name.length();
}
@Override
public boolean equals (Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof Student) {
Student anObject = (Student) obj;
if(this.name.equals(anObject.getName())&&this.age==anObject.age&&this.sex==anObject.sex&&this.score==anObject.score)
return true;
}
return false;
}
2 重写hashCode()
hashCode()是Object中的方法,每个对象的hashCode值是唯一的,所以可以理解成hashCode值表示这个对象在内存中的位置
字符串String的hashCode(),是根据内容计算的。
HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法
@Override
/*重写hashCode()方法,单纯为了检查此方法是否可以实现排重效果,所以返回一个固定的值,使所有本类对象的hashCode值都是相同的*/
public int hashCode() {
return this.name.length;
}
}
具体案例看下面综合案例
向HashSet集合中添加多个Animal对象时,所有属性都相同时,并没有完成想要的排重效果;
所以只重写hashCode方法并不能实现我们想要的排重效果
3 重写equals()
equals()方法是Object类中的方法,表示比较两个对象是否相等,若不重写相当于比较对象的地址,
所以我们可以尝试重写equals方法,检查是否排重
案例:设计一个Student类,重写equals方法,向一个HashSet集合中添加Animal对象,
检验是否排重(若所有属性都相同,视为相同元素)
代码实现:
public class Student implements Comparable<Student>{
private String name;
private char sex;
private Integer age;
private Integer score;//成绩
public Student(String name, char sex, Integer age, Integer score) {
super();
this.name = name;
this.sex = sex;
this.age = age;
this.score = score;
}
public Student() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + ", age=" + age + ", score=" + score + "]";
}
//告诉比较器,我们比较规则
@Override
public int compareTo(Student o) {
//通过返回的正数和负数来表示关系大小
if(this.getScore()>o.getScore()){
return -1;
}else{
return 1;
}
}
//重写hashcode
@Override
public int hashCode() {
// TODO Auto-generated method stub
return this.name.length();
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(obj==null){
return false;
}
Student s=null;
if(obj instanceof Student){
s=(Student) obj;
}
if(this.name.equals(s.getName())&&this.age==s.getAge()&&this.score==s.getScore()&&this.sex==s.getSex()){
return true;
}
return false;
}
}
向HashSet集合中添加多个Student对象时,所有属性都相同时,并没有完成想要的排重效果;
数据多的话很麻烦
所以只重写equals方法,也不能完全实现我们想要的排重效果。
4 HashSet集合实现排重
HashSet的重复依据: hashCode和equals
需要同时重写hashCode和equals方法,实现排重。
案例:设计一个Student类,同时重写hashCode和equals方法,检查是否实现排重
//与上列案例相似
@Override
//重写equals
public boolean equals(Object obj) {
//先判断传入的参数对象是否是Student对象,若不是直接返回false
if(obj instanceof Student) {
//若是,强转成Student对象,并比较属性的值
Student s = (Student) obj;
if(this.name.equals(s.name)) {
//若属性的值相同,则返回true
return true;
}
}
return false;
}
@Override
public int hashCode(){
/*hashCode方法返回值是int类型,所以重写时需要找到int类型的数据返回,还要保证此方法的返回值与对象的所有属性都相关,所以返回姓名属性的字符串的长度*/
return this.name.length();
}
}
同时重写hashCode和equals两个方法,可以实现元素的排重效果
5 LinkedHashSet
LinkedHashSet类是具有可预知迭代顺序(相对有序)的Set接口的哈希表和链接列表实现。是HashSet的子类。
迭代顺序和添加顺序一样
存储特点:
有序存储,不可以存储相同元素(排重),通过链表实现的集合的有序。
LinkedHashSet集合的元素排重与HashSet集合排重方法一致。
6 TreeSet集合
TreeSet集合是可以给元素进行重新排序的一个Set接口的实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的Comparator进行排序,具体取决于使用的构造方法。
存储特点:
无序存储,排重,通过二叉树实现的集合,可以给元素进行重新排序
TreeSet集合的特点是:排序
//既然是排序,那么你是不是应该告诉我们的TreeSet比较规则
/**
* 1.使泛型的javaBean来实现Compareable接口,重写compareTo方法(内比较器)(推荐使用)
* 2.TreeSet有一个有参的构造方法,传入一个Comparator类型对象(外比较器)
*/
7 SortedSet接口(了解)
TreeSet除了实现了Set接口外,还实现了SortedSet接口
SortedSet接口中常用的方法:
| 方法名 | 描述 |
|---|---|
| first() | 返回此 set 中当前第一个(最低)元素。 |
| last() | 返回此 set 中当前最后一个(最高)元素。 |
| headSet(E toElement) | 返回此 set 的部分视图,其元素严格小于toElement。(不包含) |
| tailSet(E fromElement) | 返回此 set 的部分视图,其元素大于等于fromElement。(包含) |
| subSet(E fromElement, E toElement) | 返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。 |
8 TreeSet集合的元素排序
自然排序
元素所属的类需要实现java.lang.Comparable接口,并重写compareTo方法。
compareTo方法除了可以进行排序外,还有排重的功能,但是必须在compareTo方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性
案例:设计一个Person类,实现将Person对象添加到TreeSet集合中时,对所有的元素进行排序
代码实现:
package setDemo;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
/**
* 类说明
*/
public class DVD implements Comparable<DVD>{
private String num;//编号
private String name;//片名
private String type;//类型
private boolean status;//状态
private Date lendTime;//借出时间
private Date returnTime;//归还时间
public DVD(String num, String name, String type, boolean status, Date lendTime, Date returnTime) {
super();
this.num = num;
this.name = name;
this.type = type;
this.status = status;
this.lendTime = lendTime;
this.returnTime = returnTime;
}
public DVD() {
super();
}
public DVD(String num, String name, String type) {
super();
this.num = num;
this.name = name;
this.type = type;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public Date getLendTime() {
return lendTime;
}
public void setLendTime(Date lendTime) {
this.lendTime = lendTime;
}
public Date getReturnTime() {
return returnTime;
}
public void setReturnTime(Date returnTime) {
this.returnTime = returnTime;
}
@Override
public String toString() {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd-HH:mm:ss");
String stringStatus=this.status?"已借出":"未借出";
String lt=this.lendTime==null?" ":sdf.format(this.lendTime);
String rt=this.returnTime==null?" ":sdf.format(this.returnTime);
return "编号:" + num + ",片名:《" + name + "》, 类型:" + type + ", 状态:" + stringStatus + ", 借出时间:" + lt
+ ", 归还时间:" + rt ;
}
@Override
public boolean equals(Object obj) {
System.out.println("equals方法被执行了");
// TODO Auto-generated method stub
if(obj instanceof DVD){
DVD d=(DVD) obj;
if(this.num.equals(d.getNum())){
return true;
}
}
return false;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return this.num.length();
}
@Override
public int compareTo(DVD o) {
// TODO Auto-generated method stub
if(this.num.equals(o.getNum())){
return 0;
}
return this.num.compareTo(o.getNum());
}
}
package setDemo;
import java.util.Comparator;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
public class SetDemo {
public static void main(String[] args) {
TreeSet<DVD> set = new TreeSet<>();
set.add(new DVD("CD0001", "满城尽带黄金甲", "烂片"));
DVD a=new DVD("CD0005", "哪吒", "动漫");
set.add(a);
set.add(new DVD("CD0003", "战狼", "主流片"));//
DVD b=new DVD("CD0002", "倩女幽魂", "鬼片");
set.add(b);
set.add(new DVD("CD0006", "2012", "灾难片"));
// 重复
// 迭代器迭代
Iterator<DVD> i = set.iterator();
while (i.hasNext()) {
DVD d = i.next();
System.out.println(d.toString() + "****" + d.hashCode());
}
System.out.println("*************");
for(DVD d:st1){
System.out.println(d);
}
}
}
定制排序
元素需要通过java.util.Comparator接口(比较器)中的compareTo方法进行比较大小,并排序。
compareTo方法除了可以进行排序外,还有排重的功能,但是必须在compare方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性
TreeSet集合中的无参数构造方法默认使用自然排序的方式对元素进行排序,使用TreeSet集合的定制排序时,创建集合对象不可以直接使用无参数构造方法,需要使用传入一个Comparator比较器的构造方法创建集合对象。
- 注意
java.util.Comparator 外比较器 int compare(DVD o1, DVD o2) 也可以使用匿名内部类去实现
java.util.Comparable 内比较器 int compareTo(DVD o)
代码实现:
@Override
public int compareTo(Student o) {
//定义排序,先更具成绩排序,成绩相同,年龄在排序
if(this.score>o.getScore()){
return -1;
}else if(this.score==o.getScore()){
if(this.age>o.getAge()){
return 1;
}else{
return -1;
}
}else{
return 1;
}
}
7.7 Map接口
7.7.1 概述
Map接口是将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值,值可以重复。
cn--->中国
usa--->美国
uk--->英国
us--->美国
cn--->中华人民共和国
7.7.2 Map接口的常用方法
| 方法名 | 描述 |
|---|---|
| clear() | 从此映射中移除所有映射关系(可选操作)。 |
| containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true。 |
| containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true。 |
| entrySet() | 返回此映射中包含的映射关系的 Set 视图。 |
| equals(Object o) | 比较指定的对象与此映射是否相等。 |
| get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 |
| hashCode() | 返回此映射的哈希码值。 |
| isEmpty() | 如果此映射未包含键-值映射关系,则返回 true。 |
| keySet() | 返回此映射中包含的键的 Set 视图。 |
| put(K key, V value) | 将指定的值与此映射中的指定键关联(可选操作)。 |
| putAll(Map<? extends K,? extends V> m) | 从指定映射中将所有映射关系复制到此映射中(可选操作)。 |
| remove(Object key) | 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 |
| size() | 返回此映射中的键-值映射关系数。 |
案例:
package MapDemo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @author Eric 15665627080@163.com
* @date 2019年8月14日
* @time 下午2:17:55 package MapDemo; public class Demo{ } 类说明
*/
public class Demo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("CD0001", "战狼");
map.put("CD0002", "战狼1");
map.put("CD0003", "战狼2");
map.put("CD0004", "战狼3");
map.put("CD0001", "战狼5555");//当出现重复的键时,会覆盖之前添加的内容
System.out.println(map);
//System.out.println(map.remove("CD0005"));;//根据键去删除数据,他的返回值是值
//查询
map.containsKey("CD0005");//true
map.containsValue("战狼");//true
//修改
map.replace("CD0005", "战狼", "流浪地球");//但是键不能修改
//等同于覆盖
//获取值
System.out.println(map.get("CD0001"));
//长度
System.out.println(map.size());
//遍历
/*Set<String> set=map.keySet();//将所有的键放入set集合中返回
Iterator<String> i=set.iterator();
while(i.hasNext()){
String a=i.next();
System.out.println(a+"===>"+map.get(a));
}*/
}
7.7.3 Map常用实现类
1 HashMap
基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序。
存储特点:
相对无序存储,元素以键值对形式存在,键不可以重复,值可以重复,元素整体排重,可以快速的通过键查找到所对应的值,通过哈希表实现的集合。
从底层中了解到,HashSet就是HashMap的键,所以HashSet的去重规则也适用我们HashMap的键去重(重写hashCode()和equals方法)
Map集合的排重,只需要重写键所属的类的hashCode和equals方法即可。
代码实现:
//Person作为键
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
return age + name.length();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Person) {
Person p = (Person) obj;
if(p.name.equals(name)&&p.age==age) {
return true;
}
}
return false;
}
}
//Dog类作为值
public class Dog {
private String name;
public Dog(String name) {
super();
this.name = name;
}
public Dog() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog [name=" + name + "]";
}
}
//测试类
public class Demo {
public static void main(String[] args) {
HashMap<Person, Dog> map = new HashMap<>();
map.put(new Person("zhangsan", 12), new Dog("大黄"));
map.put(new Person("lisi", 12), new Dog("旺财"));
map.put(new Person("zhangsan", 12), new Dog("二哈"));
}
}
map集合中若向集合中添加相同键的键值对时,新的值会将旧的值覆盖。
上述代码中map集合中有两个键值对,分别为:张三-12---二哈,lisi-12---旺财
2 LinkedHashMap
LinkedHashMap集合是具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。用法与HashSet类似。
存储特点:
有序存储,元素排重,通过链表实现的集合。
3 Hashtable
此类实现一个哈希表,该哈希表将键映射到相应的值。任何非null对象都可以用作键或值。
Hashtable有一个子类Properties,Properties集合使用的比较频繁。
存储特点:
相对无序存储,元素排重,通过哈希表实现的集合。
4 HashMap与Hashtable的区别
1)Hashtable线程安全的,而HashMap线程不安全的
2)Hashtable中不允许存在null的键和null值,但是HashMap中允许null的键和null值
7.7.4 Map集合的遍历
1 使用keySet方法与get方法结合
public class Demo {
public static void main(String[] args){
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 12);
map.put("bbb", 13);
map.put("ccc", 14);
//通过keySet获取map中所有键
Set<String> set = map.keySet();
//获取set的迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
//通过迭代的键,找到对应的值,一起输出
System.out.println(s+"---"+map.get(s));
}
}
}
2 使用Map.Entry方法:
调用Map集合的entrySet方法,相当于将Map集合转成一个Set集合,再通过Set集合的遍历方式遍历即可。
代码实现:
public class Demo {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);
map.put("bbb", 222);
map.put("ccc", 333);
//将map转成一个Set集合
Set<Map.Entry<String, Integer>> set = map.entrySet();
//遍历set
Iterator<Map<String, Integer>> it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
3 TreeMap
存储结构:自平衡红黑二叉树
特点:1 存储键值对、键不能重复、一个键对应一个值、值可以重复
2 数据会根据键进行排序。
3.不允许null键,允许null值
排重依据:Comparable接口的compareTo()方法的返回值。如果返回0就认为是重复的元素。
案例1:
/**
* TreeMap的使用
* 存储结构:自平衡红黑二叉树
*/
public class Demo5 {
public static void main(String[] args) {
//创建集合
TreeMap<Person, String> treeMap=new TreeMap<>();
Person p1=new Person("马云", 50);
Person p2=new Person("李彦宏", 45);
Person p3=new Person("马化腾", 40);
Person p4=new Person("马云", 50);
//1添加数据
treeMap.put(p1, "杭州");
treeMap.put(p2, "北京");
treeMap.put(p3, "深圳");
treeMap.put(p4, "shanghai");
System.out.println("元素个数:"+treeMap.size());
System.out.println(treeMap.toString());
System.out.println("---------------------比较器--------------------");
TreeMap<Person, String> treeMap2=new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int n=o1.getAge()-o2.getAge();
int n2=o1.getName().compareTo(o2.getName());
return n==0?n2:n;
}
});
treeMap2.put(p1, "杭州");
treeMap2.put(p2, "北京");
treeMap2.put(p3, "深圳");
treeMap2.put(p4, "shanghai");
System.out.println(treeMap2.toString());
}
}
集合框架小结
Collection 父接口 集合中常用方法 add() remove() contains() size() clear() iterator()
-----List:特点:有序,可以重复
----ArrayList: 存储结构 数组 适合遍历查找
----LinkedList: 存储结构 链表 适合插入 删除
----Vector 向量
----Stack 栈
-----Set: 特点:无序 ,不能重复
----HashSet:存储结构: 哈希表 无序 ,不能重复:重复依据: hashCode() equals()
----LinkedHashSet: 存储结构: 哈希表 有序
----TreeSet:存储结构:红黑自平衡二叉树 无序 ,排序 ; 不能重复:重复依据:Comparable接口 compareTo(); 返回值为0就是重复的。
Comparator:比较器,compare( o1, o2){ } 如果返回值为0 重复元素
Map 特点:1存储键值对 ,键不能重复,一个键对应一个值,值可以重复
2 无序
----HashMap:存储结构:哈希表 ,无序 ,键不能重复: 重复依据: hashCode() equals()
----LinkeHashMap:存储结构:哈希表 ,有序
----Hashtable: jdk1.0的出现的集合,不能存储null键和null值,线程安全的。
—Properties:没讲
----TreeMap:存储结构:红黑自平衡二叉树
Hash开头的存储结构哈希表 hashCode和equals
Tree开头的存储二叉树 排序 比较 Comparable Comparator
Link开头都是有序的
7.7.5 HashMap的存值原理
jdk1.8之前就是哈希表(数组+链表),1.8之后:哈希表+红黑树
//默认初始haul容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
//默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
1.hashMap的底层就是哈希表+红黑树,我们主要看hash表结构(数组+链表)
2.当第一次put值之后,那么会初始化一个容量为16的数组,将添加的元素的键经过hash算法得到hash值(hashcode),用来确定该值存放在数组的指定位置
3.当第n次put值,那么会将该值的键经过hash算法算出值,插入到指定的位置,如果该位置上已经有值了,那么此时会比较两者的内容(equals方法),如果也相同,则不能插入,是会把原来位置的值替换,当不一样,就直接挂在该元素的下面生成链表
4.当数组的使用率超过0.75时,会进行扩容为两倍
7.8 Collections工具类
此类完全由在 Collection 上进行操作或返回 Collection 的静态方法组成。对集合进行操作时,可以使用这个类中的静态方法。
7.8.1 Collections中常用方法
1.排序
/*
static <T extends Comparable<? super T>>
void sort(List<T> list)
根据元素的自然顺序 对指定列表按升序进行排序。
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(-10);
list.add(5);
list.add(3);
list.add(7);
System.out.println(list);
Collections.sort(list);//默认升序
System.out.println(list);
2.将集合中的元素进行反转
/*
static void reverse(List<?> list)
反转指定列表中元素的顺序。
*/
Collections.reverse();
3.将集合元素打乱
/*
static void shuffle(List<?> list)
使用默认随机源对指定列表进行置换。
*/
4.获取集合中的最大值、最小值
/*
static T max(Collection coll)
根据元素的自然顺序,返回给定 collection 的最大元素。
static <T extends Object & Comparable<? super T>>
T min(Collection<? extends T> coll)
根据元素的自然顺序 返回给定 collection 的最小元素。
*/
int n1 = Collections.max(list);
5.替换
/*
static <T> boolean
replaceAll(List<T> list, T oldVal, T newVal)
使用另一个值替换列表中出现的所有某一指定值
*/
//原因:List集合是不排重的,使用新的元素将集合中出现的所有的旧的元素替换掉
Collections.replaceAll(list,5,100);
6.统计指定元素在集合中出现的次数
/*
static int frequency(Collection<?> c, Object o)
返回指定 collection 中等于指定对象的元素数。
*/
int num = Collections.frequency(list,5);
7.二分法查找
/*
static <T> int
binarySearch(List<? extends Comparable<? super T>> list, T key)
使用二分搜索法搜索指定列表,以获得指定对象。
*/
//前提:必须是排好序的集合
int index = Collections.binarySearch(list,-10);
//注意:Collections工具类中的方法只操作Collection接口,主要操作的是List接口
8.集合和数组的转换
1 数组转成集合
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
//arrayToList();
//listToArray();
arrayToList2();
}
//1数组转成集合,集合不能做添加、删除操作
public static void arrayToList() {
String[] names= {"张三","李四","王五"};
List<String> list=Arrays.asList(names);
System.out.println("list:"+list);
//添加
//list.add("赵六");
//删除
//list.remove(0);
//System.out.println("添加或删除之后list:"+list);
list.set(0, "本伟");
System.out.println("替换之后:"+list);
}
//2 集合转成数组
public static void listToArray() {
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("苹果");
arrayList.add("小米");
arrayList.add("华为");
arrayList.add("三星");
//数组
String[] arr=arrayList.toArray(new String[0]);
System.out.println("--------遍历数组-------");
for (String string : arr) {
System.out.println(string);
}
}
//3特殊
public static void arrayToList2() {
Integer[] a={1,23,46,7};
//一般用于遍历
System.out.println(Arrays.asList(a).toString());
}
}
总结
Collection 父接口
|_____List (特点:有序的,可以重复)
|___ArrayList (存储结构:数组,适合遍历查找)
|___LinkedList (链表,适合添加和删除)
|___Vector 数组
|___Stack 数组(栈)先进后出
|
|_____Set(特点:无序的,不能重复)
|_____HashSet 哈希表(数组+链表+二叉树)
重复依据:1 执行hashCode()来计算存储的位置 2 执行equals比较结果
|_____LinkedHashSet 哈希表 可以保证顺序
|_____TreeSet 自平衡红黑二叉树
1 元素要实现Comparable接口 2 定制比较器 Comparator
重复依据:Comparable接口的compareTo方法的返回值0,或比较器的返回为0
Map(特点:1存储键值对,一个键对应一个值,键不能重复,值可以重复 2 无序)
|______ HashMap 哈希表 1 执行hashCode()来计算存储的位置 ,2 执行equals比较结果
|______ Hashtable 哈希表 不能存储null键和null值,线程安全的 jdk1.0
--Properties
|_______LinkedHashMap 哈希表 可以保证顺序
|_______TreeMap 自平衡红黑二叉树 1 key 要实现Comparable接口 ,2 定制比较器 Comparator
Collections工具类的使用。




1061

被折叠的 条评论
为什么被折叠?



