Java
第一个Java程序
程序的操作流程
- 1.先写出可执行的代码
- 2.在命令框之中使用javac “文件地址”运行后,生成一个class字节码文件
- 再在命令框中使用java “文件名称”运行代码
即为将class文件装载进入JVM(类加载器)当中,再去硬盘上查找该文件进行运行。 - 得到执行结果
helloworld
程序概况
-
程序如下
public class HelloWorld{
public static void main(Strings[] args){
System.out.println(“Hello World!”);
}
} -
注意:该程序在命名时,文件名称必须和public class后面的名称一致。
-
对于HelloWorld程序的解释
-
1.public 是表示公开的(关键字)
-
2.class 用力声明一个类(关键字)
-
3.HelloWorld 是一个类名
-
4.每一句Java语句都必须用;结尾,代码自上而下执行。
-
main 即为程序的入口,又叫做主方法,main后{}中的称为方法体。
如何给Java程序添加注释
注释的三种方式
-
第一种:单行注释
- //单行注释,两个正斜杠后面的内容为注释内容。
-
第二种:多行注释
-
/*
-
*这里的注释信息为多行注释
-
*第一行注释内容
-
*第二行
-
*/
-
-
第三种:javadoc注释
-
/**
-
*@author 作者名字
-
*@version 版本号
-
*@since 自从哪个版本开始就存在了
-
*/
-
对于Javadoc注释来说,这里的注释内容会被javadoc.exe解析并生成帮助文档。
-
注释应该怎么写
- 通常在类和接口处写,这一部分的注释是必须的。在这里我们可以使用javadoc.exe注释,表明创建者,时间,版本,以及类的应用。
- 对于入参,出参,返回值,均要标明。
- 对于常量,标明该常量的用途。
public class和class的区别
-
在同一个Java文件之中,public class定义的公共类名只能有一个,而且这个文件若是A.java 则被public class定义的类名只能是A。
-
在源文件之中,我们可以使用class创建多个类来使用,但是类的名称不能重复,在每一个类中我们都可以编写main方法,想让程序从哪个入口执行,则加载哪个类即可。
-
Java基础语法
标识符
标识符的命名规则
-
标识符只能以数字,字母,下划线,美元符号组成。
-
标识符不能以数字开头。
-
Java关键字和保留字不能作为标识符。
-
严格区分大小。
-
没有长度限制。
关键字
关键字即是Java系统中自己定义的常量,有些表示数据类型,或是数据结构。
字面值
字面值其实就是数据
Java中的字面值
-
1.整数型字面值
-
2.浮点数型字面值
-
3.布尔字面值
-
4.字符串型字面值
-
5.字符字面值(单引号括起来的单个字符)
数据类型
-
例如:
float i=1.0;
int n=(int)i;注意:
-
在数据的强制转化之中,可能会造成精度丢失,要慎重。(大容量转向小容量,即为上面的例子。)
-
也有一种转化为自动转化,即为小容量向大容量转化。
-
不同的类型在进行运算时,先转化成为容量最大的那个类型,再进行运算。
难点
-
对于下面的程序
byte a=3;
byte b=a+4;
byte c=3+4;- 这个程序的第二句会报错,然而第三句却不会,这是为什么呢?
- 因为第二句中是byte类型加上int类型,他们会先转化为int再进行运算,编译器认为byte+int可能会超出byte的值,而且a还是一个变量,所以报错了。但是第三句中,3和7都是常量不是变量,可以直接计算出来是7,而这个7也没有超出byte的范围,所以编译又通过了。
- 这个程序的第二句会报错,然而第三句却不会,这是为什么呢?
数据类型转为字符串如何转
- 只需要在数据后面加上加号输出即可。
public class StringTurn {
public static void main(String[] args) {
int i =1;
double e= 2.11;
float x=3.11f;
String i1=i+“”;
String e2=e+“”;
System.out.println(i1);
System.out.println(e2); }
}
那么字符串类型转为其他数据类型该怎么办呢?
语法:需要用到基本类型的包装类调用parseXX方法即可
-
要转换谁,就使用基本类型的包装类调用parseXX方法
- eg:转换为整数使用Integer.parseInt()
- Float.parseFloat()
- eg:转换为整数使用Integer.parseInt()
-
Double.parseDouble()
-
Bollean.parseBoolean()
-
那么字符串如何转化为字符类型呢,我们需要的是把字符一个一个取出来,就像python的集合一样
- eg:System.out.println(s.charAt(0));此刻我们假设s为一个字符串,我们取出他的第一位。
-
-
运算符
算数运算符
-
+(加),-(减),*(乘),/(除),%(取余符号)
单目运算符
-
++,对数字自加1
-
–,对数字自减1
-
Java中规定,当单目运算符出现在变量之后时,会先做赋值运算。即=的优先级在此时高于++。
- 比如b=a++,此时会先将值赋给b再进行加一运算,那么b=++a,此时a会先进行加一运算再赋予b。
-
下面我给出一道经典的面试题
int i=1;
i=i++;
System.out.println(i);
i最后等于什么?为什么?- 这其中有一个临时变量,我来写出每一步
1.temp =i 2.i=i+1 3. i=temp
- 这其中有一个临时变量,我来写出每一步
-
其中temp就是电脑执行这行命令时,所创造出的临时变量。
-
关系运算符
-
>,>=,<,<=,==,!=
-
此关系符输出的是bool值
逻辑运算符
-
&逻辑与
-
ture&true——true
-
flase&ture———flase
-
-
|逻辑或
- ture|flase——true
-
!逻辑非
-
!true——flase
-
!flase——true
-
-
&&短路与
-
||短路或
-
短路,即为惰性原则,当&&判断出第一个是flase时,不会去计算后面的式子,而是直接输出flase,这就叫发生了短路。
-
运算结果为bool值
赋值运算符
-
=
-
+=
-
-=
-
*=
-
%=
-
/=
-
这几种全是赋值运算符,除去第一个,其他的全都是式子的简写,我以第二个为例子
- a+=1就是a=a+1,其余的也一样,可以在编程中简化代码
字符串连接运算符
-
字符串连接符即为➕,当➕两边有一边是字符串类型时,会进行字符串的拼接。
-
字符串拼接时,会从左向右依次执行eg:
a+“+”+b+“=”+a+b
- 第一个加号是字符串,第二个是字符串连接符,执行下来的结果为a+b=a+b
-
当左右连接的是字符串时,会将其连接,但是如果是字符时,会将其转换成为阿斯克码再进行相加的运算。
条件运算符(三目运算符)
-
语法结构: 布尔表达式:表达式1?表达式2;
-
若布尔表达式为true,执行表达式1,若为flase,执行表达式2。
位运算符
- &按位与,|按位或,^按位异或,~按位取反,<<左移,>>右移(带符号),>>>不带符号右移。
其他运算符
-
instanceof
-
new
控制语句
选择语句
if
-
if语句一般的编写方式如下
if(布尔表达式){
java语句;
}
else if(布尔表达式){
java语句;
}
else{java语句;
}- 如果布尔表达式值为true就执行当前if语句中大括号内的代码。
switch
-
switch的编写方法一般如下
switch(int/string){
case int/string:
java语句;
break;
case int/string:
java语句;
break;case int/string:
java语句;
break;
default:
java语句;}
- 当switch括号内的值等于case括号内的值时,就执行当前case语句下面的java语句,如果没有匹配的值,就执行default下面的java语句
-
注意:每一个case的java语句写完后,都要加上break语句来终结本次选择语句的执行。
循环语句
for
-
for语句的语法结构如下
for(初始化表达式;布尔表达式;更新表达式){
java语句;
java语句;
java语句;
java语句;
}- for循环中,先执行初始化表达式,再判断布尔表达式,如果布尔表达式为true,则开始执行循环体,当执行完循环体时,再执行更新表达式,最后再对布尔表达式进行判定,如果为ture则继续执行循环体,如果为flase就结束循环。
while
-
while循环结构如下
while(布尔表达式){
循环体;
}- 当布尔表达式为true时,循环会一直执行下去,直到布尔表达式为flase。
do……while
-
do……while循环结构如下
do{
循环体;
}while(布尔表达式);- 它会先执行循环体,再进行条件判断,如果为true,则继续循环,如果为flase就终止循环。
转向语句
-
break
- 被用于循环体中,用来跳出循环。
-
continue
- 被用于终止本次循环,继续进行下一次循环。
返回语句
- return
- 当函数有调用时,用于返回值到函数调用处。
方法(内含Java虚拟机的内存管理)
方法的作用
- 方法的出现,极大的提高了编程的效率,当一个功能需要重复实现时,就可以用上方法来简化程序。
方法的声明和调用
-
方法的定义语法如下
[修饰符列表]返回值类型 方法名(形参列表){
方法体;
}-
修饰符列表,这个是可选的不是必须的。
-
返回值类型,可以是Java的任何一种数据类型,当一个方法执行完成后,没有返回值时,必须写成void。
-
方法名,这项必须是合法的标识符,可以看出来方法的作用。
-
形式参数列表,当有多个形参时,通常使用“,”进行分离。
-
方法体,由Java语句构成,代码遵循自上而下的顺序依次执行。
-
-
当写完一个方法的定义后,这个函数就已经声明了。
-
方法的调用实在主函数中,写出“类名.方法名.(实际参数列表);”
-
有些情况下,类名可以被省略。
- 当在方法a中执行方法b时,而且方法a与方法b的类相同时,b的类名可以被省略。
方法的形参和实参
-
方法在调用时,其实参必须和形参一一对应
-
形参和实参类型也必须一一对应,当然有些时候会出现类型的自动转化,比如int转long
方法的返回值
-
返回值会使用return语句,返回我们需要的值回到方法调用处。
-
方法的返回值必须和定义函数时写下的类型相同,不然会报错。
方法的重载和调用时其内存变化
-
要知道内存变化,我们先要了解一个数据结构:栈(stack)
-
如上图所示,向一个栈插入新元素,叫做进栈,入栈或是压栈(push),它将新元素放在栈顶元素的上面,使他成为新的栈顶元素;同样的将栈的元素删除称为出栈,退栈又称弹栈(pop),它是将栈顶元素删除来实现。
-
栈的限制是:仅允许在表的一边进行添加和删除的操作。
-
知道了栈的数据结构后,我们来看一张图
- 当Java程序开始实现时,先通过类加载器系统找到字节码文件,再将其加载到虚拟机中的方法区当中,开始调用main方法,main方法在被激活的瞬间,会在栈中给他分配活动空间,此时是压栈动作,main在栈底。- 当Java程序执行完成后,会释放内存发生弹栈动作,main方法最后一个被放出,所以我们说当main方法结束时,这个程序也就结束了是不无道理的。#### 对于图上各个点的解释##### 线程的概念- 线程是操作系统中能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
程序计数器
- 概念:可以看做当前线程所执行的字节码的行号指示器。- 特点:线程私有的内存。
Java虚拟机机栈
- 概念:描述的是Java方法执行的内存模型
- 特点:线程私有,生命周期和线程相同。这个区域会出现两种异常。分别是StackOverflowError异常:线程请求深度大于虚拟机所允许的深度。 OutOfMenmoryError异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。
本地方法栈
- 概念:他与虚拟机栈所发挥的作用是相似的,区别是Java虚拟机栈为Java方法服务,而本地方法栈为本地方法服务。- 特点:他也有和Java虚拟机机栈相同的异常。
Java堆
-
概念:是被所有线程共享的一块区域,在虚拟机创建时创建。
-
特点:线程共享,存放的是对象实例,GC管理的主要区域可以处于物理上不连续的区域。
方法区
-
概念:存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
-
特点:线程共享区域,抛出异常OutOfMemory异常:当方法去无法满足内存分配需求时。
方法重载
方法重载的定义:
是在一个类中定义多个同名的方法,但要求每个方法具有不用的参数类型或者是参数个数
作用:
在调用重载方法时,java编译器可以通过检查调用方法的参数类型和参数的个数来选择一个合适的方法。
满足的条件
- 1.在同一个类中
- 2.方法名相同
- 3.参数列表不同
- 个数不同算不同
- 顺序不同算不同
- 类型不同算不同
递归
递归的定义
递归就是在m()之中又调用了m()方法,方法调用方法自身,就构成了递归。
注意事项
-
1,递归函数得在自己的函数中调用自己,才能一步步自己执行下去。
-
2,递归函数得自己设定结束的标志,不然会一直运算下去
生活实例
可以解决各种生活问题,比如汉诺塔,阶乘问题,迷宫问题,球和篮子的问题,算法中也会用到递归,比如快排,归并排序,二分查找等
例子:计算1~n的和
public class Test{public static void main(Strings[]args){int n=99;int result=accumulate(n);System.out.println("1~n的和是"+result);}public static int accumulate(int n){int result=0;if(n==1){return 1;}return n+accumulate(n-1);}
例二:汉诺塔
规则:1、有三根相邻的柱子,标号为A,B,C。
2、A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘。
3、现在把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。
代码实现:
public class hannuota {public static void main(String[] args) {class Tower{public void lujing(int num,char a,char b,char c){if(num==1){System.out.println(a+"->"+c);}else{lujing(num-1,a,c,b);System.out.println(a+"->"+c);lujing(num-1,b,a,c);}}}Tower t=new Tower();t.lujing(n,'A','B','C');}
}
例三:小老鼠走迷宫
小老鼠需要从左上角走到右下角,该怎么实现?
public class mouse {public static void main(String[] args) {int [][]migong=new int[8][7];for(int i=0;i<7;i++){migong[0][i]=1;migong[7][i]=1;}for(int i=0;i<8;i++){migong[i][0]=1;migong[i][6]=1;}migong[3][1]=1;migong[3][2]=1;
for(int i=0;i<migong.length;i++){for(int j=0;j<migong[i].length;j++){System.out.print(migong[i][j]);System.out.print(" ");}System.out.println(" ");
}System.out.println("==============");
class a{public boolean foundway(int [][]a,int i,int j){if(a[6][5]==2){return true;}else if(a[i][j]==0){a[i][j]=2;if(foundway(a,i+1,j)){return true;}else if(foundway(a,i,j+1)){return true;}else if (foundway(a,i-1,j)){return true;}else if(foundway(a,i,j-1)){return true;}else{a[i][j]=3;return false;}}else {return false;}}
}
a A=new a();A.foundway(migong, 1, 1);for(int i=0;i<migong.length;i++){for(int j=0;j<migong[i].length;j++){System.out.print(migong[i][j]);System.out.print(" ");}System.out.println(" ");}}
}
红色的就是小老鼠走过的路程。
this和static的用法
this
this可以看作一个引用,储存在java虚拟机内部,this这个引用保存了当前对象的内存地址指向自身,每一个堆内存的java对象都有一个this。
this引用和变量名引用其实是可以画等号的,都是指向对象的属性。
this不能出现在static的方法里面,因为static方法里面调用时不需要创建对象,直接采用类名调用,所以不用使用当前对象调用,因为this是和当前对象划等号的。
换句话来说this是出现在实例方法里面的。这个实例方法里面正在调用的对象是谁,this就是谁。
this在构造方法中
使用在构造方法的第一行,调用他时的语法格式是this(实际参数列表)。
很大程度上来说this简化了程序,当实参进入程序时,实参名等于this名,可以直接使用,提高了代码的可维护性。
static
表示”静态的“,它可以被使用来修饰变量,方法,代码等,修饰方法时叫做静态方法,变量叫静态变量,代码叫做静态代码块。
凡是使用static修饰的都是与类相关的,直接通过类名访问。
static使用时,即代表这个数值不会根据对象的改变而改变,如果根据对象的改变而改变了,那么就应该时实例对象,而不是静态对象。
静态代码块
-
就是在代码块外围大括号前面写上static。
-
他会在类加载时运行,并且只执行一次。
-
在静态代码块中出现实例变量会报错,我们只需要在给实例变量定义时前加上static即可,这表示该变量是一个静态变量。
静态方法
-
-
一般是工具类的方法,会有利于开发,方便调用,直接使用类名就可以使用。
输入语句
用到了扫描器scanner=>他是简单文本扫描器
步骤
第一步:导入包,java.util.Scanner
第二步:创建一个对象,就是new()在面向对象中写过如何创建一个对象。
第三步:接受键盘输入
数组(引用类型)
定义一个数组
- 比如:double [ ]hens ={1,5,9,7,9,5};
- double代表这个数组的类型,hens代表这个数组的名称。
遍历数组(利用for循环)
-
eg:利用for循环从hens[i]来循环,i=0然后慢慢递增,直到第5个,就将这个数组遍历完了。
-
这是代码
-
这是执行后的成果
数组的使用及其初始化
初始化方法
-
1.静态初始化
-
2.动态初始化
-
3.直接赋值
-
使用细节
-
1.数组是多个同类型数据的组合,实现对这些数据的统一管理。
-
2.不能高精度值赋给低精度类型,比如不能把double类型的数据放入int数组。
-
3.数组的数据类型,不能混用。
-
4.在数组创建后,没有的赋值时,默认值为0。
-
5.数组是引用类型,数组型数据是对象(object)。
数组的赋值规则
-
拷贝赋值
-
就是基础类型之间的直接赋值,比如下面的代码,当我们改变n2的值时,n1不会受到影响。这与jvm的内存系统有关。
int n1=10;
int n2=n1;
in2=20; -
在给n1赋值时将10直接给了n1,在执行n2=n1时,会将n1的值10直接拷贝到n2的值上,这就叫做拷贝复制,当我们改变n2的值时,n1便不会受到影响。
-
-
引用赋值
-
数组在默认情况下是引用传递,赋的值是地址,赋值的方式为引用赋值。
-
下面我用一段代码来说明
int []arr1={1,2,3};
int []arr2=arr1;
arr2[0]=10;- 当我改变了arr2[0]的值时,arr1这个数组的值也会改变,这是因为在jvm里面存放时,数组指向的不是具体的值,而是一个空间,这个空间的名字叫地址,这个空间里面来存放数据,当我们将arr1数组赋给arr2数组时,是将他的地址赋给了arr2,那么此时arr2这个数组,也可以直接访问这个地址,当我们改变arr2的值时,相当于改变了这个地址上面的值,arr1也会随之改变。
-
-
数组拷贝(得到新数组,不和上一个有关联)
-
1.先new一个新数组,int arr1=new int [arr1.lenght];
-
2.然后使用for循环遍历救数组,将旧数组的值赋给新数组。
-
数组扩容
-
给int arr[]={1,2,3};后面扩容,加上4,使其变成{1,2,3,4}
-
方法
-
1.定义初始数组
-
2.定义一个新的数组
-
3.遍历初始数组,依次将初始数组的元素拷贝到新数组上
-
4.让初始数组指向新数组,那么原来的数组将会被销毁
-
代码实现
-
原理
- 主要是最后一步的指向,是将新数组的地址赋给旧数组的地址,从而使得旧数组的地址被销毁。
-
-
那我们现在写一个程序让用户自己决定什么时候停止给数组扩容
import java.util.Scanner; public class shuzu {public static void main(String[] args) {int a[] = {1,2,3};do {Scanner s=new Scanner(System.in);System.out.println("input number");int w=s.nextInt();int newa[]=new int[a.length+1];for(int i=0;i<a.length;i++){newa[i]=a[i];}newa[a.length]=w;a=newa;for(int x=0;x<newa.length;x++){System.out.println(a[x]);}System.out.println(" if you want continue please input 1");int x=s.nextInt();if(x==1){break;}}while(true);} }
-
数组中查找东西
-
1.定义一个字符串数组。
-
2.接受用户输入,遍历数组,进行逐一比较,如果有,则提示信息,并推出。
-
代码实现
import java.util.Scanner; public class found {public static void main(String[] args) {String a[]={"mike","anna","davi"};Scanner d=new Scanner(System.in);System.out.println("please input name");String name=d.next();for(int i=0;i<a.length;i++){if(a[i].equals(name)){System.out.println("yes,you found it");}else if(i==a.length-1){System.out.println("sorry");}}} }
-
多维数组
-
多维数组
-
二维数组
-
-
什么是二维数组
- 从定义和形式上看,就是原来的每个元素都是一个数组,这样就是二维数组。
-
两种创建方法
- 第一种直接赋值创建
- 就是直接赋值的方法
-
第二钟动态创建
int arr[][]=new int[2][3];
arr[1][1]=3;-
列数不确定创建,即为一维数组中的元素不相同。
-
int[][]arr=new int [3][];
-
就是给上面的一维数组开空间,然后可以分别给一维数组的每个元素赋值。
-
-
-
遍历方法
-
套用两层循环来实现
-
代码实现
public class DoublEshuzu { public static void main(String[] args) {int a[][]={{1,0,1,3},{5,4,1,4},{5,4,8,7}};for(int i=0;i<a.length;i++){for (int j=0;j<a[i].length;j++){System.out.print(a[i][j]+"\t");}}} }
-
输出10行杨辉三角
public class YangHui {public static void main(String[] args) {int [][]a=new int[10][];for(int i=0;i<a.length;i++){a[i]=new int[i+1];for(int j=0;j<a[i].length;j++){if (j == 0 || j == a[i].length-1){a[i][j]=1;}else{a[i][j]=a[i-1][j]+a[i-1][j-1];}}}for(int i=0;i<a.length;i++){for (int j=0;j<a[i].length;j++){System.out.print(a[i][j]+"\t");}System.out.println(" ");}}}
-
-
-
作业
随机生成10个整数(1~100)保存到数组,并倒序输出求出其打印值,求最大值和最小值的下标,并查找里面是否有8
代码实现
public class homework {public static void main(String[] args) {int []b;int sum =0;double HalfSum=0;b=new int[10];for(int i=0;i<10;i++){int a=(int)(Math.random()*100+1);b[i]=a;}int max=b[0];int min=b[0];for(int j=9;j>=0;j--){System.out.print(b[j]+" ");if(b[j]==8){System.out.print("yes");}sum += b[j];HalfSum=sum / 10;}System.out.println(" ");System.out.println(sum);System.out.println(HalfSum);for(int k=0;k<10;k++){if(max<b[k]){max= b[k];}if(min>b[k]){min=b[k];}}for(int f=0;f<10;f++){if (b[f]==max){System.out.println("max is"+f);}if(b[f]==min){System.out.println("min is"+f);}}}}
类与对象
为什么引入类与对象这个方法,我们从一个问题中来得到答案
王奶奶有两只小猫,一只5岁黑色公猫,一只6岁黄色母猫,现在编写程序来输入一只猫,得到这只猫的所有信息,如果输入的猫不是王奶奶的猫,则说明错误。
这个问题中,我们首先想到使用变量来实现,我们会发现,变量太多代码程序繁杂,那么我们再考虑使用数组来实现,数组在使用的过程中,我们不能统一数据类型进行使用,也是行不通的。
这时候,我们使用类与对象的方法来解决这个问题。
类和对象的关系
- 把猫所有的特性提取出来构成一个猫类,这些特性的数据类型各不相同,把每个猫的特性数据集合到一起,构成一个对象。这个对象就是一个具体的实例。
代码实现
public class oop {public static void main(String[] args) {class cat{int age;//这里进行了猫类的创建,并且在猫类里面说明了猫的特性。}cat cat1=new cat();//创建了猫对象cat1.age=10;//给cat1对象的属性赋值System.out.println(cat1.age);}
}
如果要增加猫的属性,比如名字,直接再class cat里面加入名字这个特性即可。
对象在内存中的存在形式
基本数据类型会直接存储到堆里使用,非基本数据类型比如说字符串,会在堆里面存储地址,这个地址指向方法区里面的空间,在方法区这个空间存储变量。
属性和成员变量
基本概念:从概念和叫法上看,成员变量等于属性等于field(字段)
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型,比如说我们在猫类里面定义的age就是一个属性。
注意事项和细节说明:
- 属性定义语法和变量相同,需要输入相同类型的值。
- 属性的定义类型可以为任何类型,包含基本类型和引用类型。
- 属性不赋值的情况下,有默认值,规则和数组相同。
对象其实是他里面的东西,cat1只是他的对象名,真正的对象是new出来的对象空间,即为new cat() 。
成员方法
基本概念:在某些情况下,我们需要定义成员方法,比如人类除了年龄性别这些属性外,还有一些行为,这些就需要通过成员方法来完善。
public class oop {public static void main(String[] args) {class cat{int age;public void eat(){System.out.println("i like eat");}}cat cat1=new cat();cat1.age=10;cat1.eat();//调用eat方法System.out.println(cat1.age);}
}
这里的eat方法就是我定义的成员方法。
成员方法在jvm内存的储存空间
成员方法的定义方法
public 返回类型 方法名(形参列表){方法体内的语句;return 返回值;
}
public是访问修饰符,有四种类型,用于控制方法生效的范围
四种分别是:public, 不写, potected, private。
细节问题
方法不能嵌套定义,即不能在方法里面定义方法
如何跨类调用方法
class A{public void m(){System.out.println("调用了a类中的m方法");}public void n(){System.out.println("调用了a类中的n方法");}
}
class B{public viod am(){A a =new A();a.m();a.n();}
}
B b=new B();
b.am();
成员方法的传参机制
基本类型的传参机制
在方法中修改方法外部的数值,只要不return回去重新赋值,这个方法在执行完后会直接释放内存,不会影响外部的数值。不会修改地址。
引用类型的传参机制
比如数组来说,传递进去的是数组,即为地址,不是值。假如现在有一个方法用于交换数组当中的值,方法在调用时会,开辟一个新的栈来使用,这个方法在使用时,会对堆里面的地址进行改变,当方法执行完成后,这个方法栈被释放内存,但是这个堆里面的地址会永久的改变,这与基本类型不同,传递参数会影响到外部的值。会修改地址。 还有一种是传入对象,对象的本质也是地址,综上所述,这个只要传入的是地址,就会改变值。
如何将对象作为参数传入方法中呢?
比如现在的类是Person,在方法()里面输入Person b即可,这示意在Person建立一个对象b传入方法之中。
了解了这么多,我们可以实现对象的克隆,属性情况都相同,但是输出的是两个不同的对象,下面是代码实现。
public class oop {public static void main(String[] args) {class Person {int age;String name;double high;}class copy {public Person copyPerson(Person b) {Person a=new Person();a.high=b.high;a.name=b.name;a.age=b.age;System.out.println(a.age);return a;}}Person b=new Person();b.age=11;b.name="mike";b.high=187.2;copy x=new copy();Person a=new Person();a=x.copyPerson(b);}
}
注意的点
一个是return出来的值需要去接收,所以我重新建立了一个新的对象a来接收方法执行中被克隆出来的对象。
第二个是方法如果要返回这个对象,他的返回值类型不能为void而是要返回的类名称,即为Person。
第三个是要使用方法时,需要先建立这个类的对象,再对象.方法名(),进行shi’y
重载(OverLoad)
基本介绍
介绍
Java中允许在一个类中,多个同名方法存在,但是要求形参列表不一致。比如说输出打印的方法,System.out.print()这个函数,什么都可以输出,比如说字符串,字符,布尔值,数字啥的。在这里的out其实就是一个对象,形参都不同,这种就叫方法的重载。
好处
1,减轻了起名的麻烦。
2,减轻了记名的麻烦。
例如
public class overload {public static void main(String[] args) {class num{public int num(int a, int b){return a+b;}public double num(double a,int b){return a+b;}public double num(int a,double b){return a+b;}public double num(double a,double b){return a+b;}}num n=new num();System.out.println(n.num(5.3,6));System.out.println(n.num(5,4));System.out.println(n.num(6.6,4.2));}
}
运行正常,例子我用的是两位数的和方法,可以输出整数或者浮点数。
细节
方法名:必须相同。
参数列表:必须不同。(形参类型,形参个数不同都可以构成方法重载)
返回类型:无要求。
练习题
在m类中定义max()方法,输入两个int值时输出较大值,输入两个double值时输出最大值,输入三个double值时输出最大值。
public class overload {public static void main(String[] args) {class m {public int max(int a, int b) {if (a > b) {return a;} elsereturn b;}public double max(double a, double b) {if (a > b) {return a;} else return b;}public double max(double a, double b, double c) {if(a>b&&a>c)return a;else if (b>a&&b>c)return b;else if (c>a&&c>b)return c;return 0;}}m m=new m();System.out.println(m.max(5,6));}
}
可变形参
基本概念
Java中允许将同一个类中多个同名同功能但是参数个数不同的方法,封装成为一个方法。这里就可以通过可变参数来实现。
快速入门案例
比如在重载中,我们加多个int类型的值,相加两个,相加三个,就得写两个方法,但是我们使用可变参数,就可以把他整合成为一个方法。
public class VarParameter{public static void main(Srting[] args){HsMethod m= new HsMethod();m.sum();}
}
class HsMethod{public int sum(int... nums){int res =0;for(int i=0;i<nums.lenght;i++){res += nums[i];}return 0;}
}
我们现在观察上面的代码,其中的int… nums就代表可以接收int类型的值,数量是从0到n,这些数值会被保存到nums这个数组里面,当我们需要将其相加的时候,我们只需要遍历这个数组即可,极大的简化了程序。
注意事项
- 可变参数的实参为0个或者至多个。
- 可变参数的实质为数组。
- 可变参数可以和普通类型的参数一起放在形参列表,但需保证可变参数在最后放置。
- 一个形参列表只能出现一个可变参数。
练习
有三个方法,分别实现返回姓名和两门课程的总分,返回姓名和三门课的总分,返回姓名和五门课的总分,封装成一个可变参数的方法。
public class overload {public static void main(String[] args) {class b{public void student(String a,int... sums){int res=0;for(int i=0;i<sums.length;i++){res +=sums[i];}System.out.println(a);System.out.println(res);}}b a=new b();a.student("mike",4,5,7,8,9);}
}
作用域
基本使用
- 在java编程中,主要的变量就是属性(成员变量)和局部变量。
- 局部变量一般是指在成员方法中定义的变量。
- 全局变量:就是属性,作用域为整个整体。
- 局部变量:也就是除了属性以外的其他变量,作用域为定义它的代码块中。
- 全局变量可以不进行赋值,直接使用,因为它具有默认值,局部变量必须进行赋值后才可以进行使用,因为没有默认值。
注意事项和细节
- 属性和局部变量可以重名,访问时遵循就近原则。
就近原则:比如有一个属性叫name=mike,在方法中的name=king,假如我们在创建对象后使用该方法输出name值,根据就近原则,会输出king这个值。那么我们接下来将方法中的name值给注销,再进行输出,就会输出mike这个值。 - 在同一个作用域中,两个变量名不能重名。
- 属性的生命周期长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变量,生命周期短,伴随着它的代码块执行而创建,伴随着代码块的结束而死亡,即在一次方法的调用中生效。
- 范围不同
全局变量:可以被本类使用,也可以通过在其他类中通过对象调用来在其它类中使用。(一种是跨类调用,另外一种是将类当参数传入方法使用)
局部变量:只能在本类中的对应方法使用。 - 修饰符不同
局部变量不可以加修饰符。
全局变量可以添加修饰符。eg:public ~ ~ ~
构造器constructor
什么时候使用?
当我们需要创建人类这个对象时,是先把一个对象创建好后,再给他的年龄和名字属性赋值,有了构造器,我们可以在创建人类对象时,直接指定这个对象的年龄和姓名。
作用:初始化新对象
基本语法
【修饰符】方法名(形参列表){
方法体;
}
说明:
- 这里的修饰符可以不写,有默认值,也可以是public protected private
- 构造器没有返回值。
- 方法名必须和类的名字相同。
- 参数列表和成员方法一样的规则。
- 构造器的调用是由系统完成的。
快速入门
public class constructor {public static void main(String[] args) {class Person{String name;int age;public Person(String a,int b){name=a;age=b;}}Person p= new Person("mike",18);System.out.println(p.age);System.out.println(p.name);}
}
第十一行中在初始化对象时就定下了属性,输出结果正常。
注意事项和使用细节
-
一个类可以定义多个不同的构造器,即为构造器重载。(可以一个只给名字,一个给名字和年龄等等)
-
构造器名要和类名相同。
-
构造器没有返回值。
-
构造器是完成对象的初始化,不是创建对象。
-
在创建对象时,系统自动调用该类的构造方法。
-
如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造方法)比如Person(){},使用javap反指令反编译看看。
class Person {}
平常创建对象时,进行的 Person p=new Person();
这个小括号其实就是运行了默认的构造器。
反编译工具 javap
在cmd命令框里面运行
- 一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能使用默认的无参构造器,除非显式的重新定义一下,例如:Person(){}
对象创建的基本流程
public static void main(String[] args){class b{int age;String name;public b(int a,String b){age=a;name=b;}}b B=new b(18,"mike");
}
在第10行中的代码,首先执行的是创建一个新对象,这个对象并不是B,这个对象是指向B的地址,即会在栈中创建一个地址用来存放值,当没有在b()中输入时,地址中的值为默认值,为null和0。在输入具体值后,会对默认值进行覆盖,至此,一个对象的创建便完成了。对象的实质是在栈中开辟的空间
this关键字
什么是this?
我们用一个案例来说明
public static void main(String[] args){class b{int age;String name;public b(int age,String name){age=age;name=name;}}
}
这里的形参为了方便我们看,我将它改为了属性名,但是这里存在一个问题,根据作用域的特点,这个方法中的name根据就近原则并不是属性名,而是形参,从这点来看,name=name和age=age就没有任何作用,这时候就引出了this关键字,这个关键字可以解决这个问题。代码实现如下
public static void main(String[] args){class b{int age;String name;public b(int age,String name){this.age=age;this.name=name;}}
这里的this就代指此处调用b()方法的对象,如果对象w调用b()方法,那么此时的this就代表w,实质上是w.name=name。
深入理解
对于内存存储的理解,在创建这个对象时,this作为隐藏属性存在其堆中,当使用this这个属性时,this会指向方法区中的一块地方,这个地方叫常量池,常量池中存在着对象的地址,使得this可以代替对象进行方法的调用,下面是图解
说白了,this会指向自己所在的对象空间,从而使方法得以使用。
使用细节
- 访问成员方法的语法
class{public void t1(){System.out.print("调用了f1方法");}public oid t2(){System.out.print(" 调用了f2方法");f1();this.f1();}
}
其中如果想要在f2中调用f1方法,可以为第七行,也可以为第八行。
-
this还可以访问构造器,注意只能在构造器中使用(只能在构造器中访问另外一个构造器)。
如果有this访问构造器这种语法,必须置于程序的第一行去执行。A e=new A(); class A{public A(){this("jake",100);System.out.print("使用了public A()构造器");}public A(String name,int age){System.out.print("使用了public A(String name,int age)构造器");} }
程序中创建了a对象,使用了无参构造器,然后在无参构造器中访问了第二个构造器,进行访问。
- this不能在类定义的外部使用,只能在类中使用。
练习
定义Person类,里面有name,age属性,并提供compareTo比较方法,用于判断是否和另外一个人相等,提供测试类TestPerson用于测试,名字和年龄完全一样,就返回true,否则返回false。
public class thiS {public static void main(String[] args) {class Person{String name;int age;public boolean compeTo(Person a,Person b){if(a.age==b.age&&a.name==b.name){return true;}else return false;}}} }
这是不使用this关键字的程序,下面我使用this关键字来写一段。
public class thiS {public static void main(String[] args) {class Person{String name;int age;public Person(String name,int age){this.name=name;this.age=age;}public boolean compareTo(Person p){return this.age==p.age&&this.name==p.name;}}Person p=new Person("mike",17);Person k=new Person("mike",17);System.out.println(p.compareTo(k));} }
Java中级编程
包
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
包的基本语法
package com.hspedu;
说明:
- package关键字,表示打包。
- com. hspedu 表示包的名称
包的命名
命名规则:只能包含数字,字母,下划线,小圆点,不能是关键字或保留字。
命名规范:一般是小写字母加小圆点eg:com.oa.model这种
常用的包
java.lang* 基本包,不需要引用,直接使用
java.utrl.* util包,系统提供的工具包,工具类,比如使用scanner
java.net.* 网络包,做网络开发
java.awt.* 做java的界面开发,GUI
导入包
import java.utrl.Scanner
import java.utrl.*
第一种是导入这个包里面的的scanner方法
第二种是直接导入这个包
在编程过程中,推荐使用第二种方法。
使用细节和注意事项
- package的作用是声明当前类所在的包,xvyaofangzaiclass的最上面,一个类中最多只能有一个package
- import指令位置放在package的下面,在类定义的前面,可以有多句,而且没有顺序要求。
访问修饰符
基本介绍
java提供四种访问控制修饰符号,用于控制方法和成员变量,属性的访问权限。
- 公开级别:使用public修饰,对外公开。
- 受保护级别:用protected修饰,对子类和同一个包中的类公开。
- 默认级别:没有修饰符,向同一个包的类公开。
- 私有级别:用private修饰,只有类本身可以访问,不对外界公开。
注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类。
- 只有默认的和public才能修饰类,并且遵守以上的访问权限的特点。
封装(encapsulation)
就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作方法,才能对数据进行操作。
好处
- 隐藏实现的细节。
- 可以对数据进行验证,保证安全合理。
封装实现的步骤
- 属性私有化,让外部不能修改属性。
- 提供一个public方法,用于对属性判断并赋值。
- 提供一个公共的get方法,用于获取属性的值。
快速入门案例
设计一个小程序,不能随便查看别人年龄,工资等隐私。并对设置的年龄进行合理的验证,年龄合理就设置,否则给默认年龄,年龄介于1~~~~~100,姓名为2-6个字符
package test.encapsulation;
public class enca {public static void main(String[] args) {person a=new person();a.setName("m");String b=a.getName();System.out.println(b);}
}
class person {String name;private int 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;}
}
这样写get方法和set方法太慢了
我们可以使用快捷键来写alt+ins来找到get和out方法来完成。
这个程序中的age是私有类别,无法访问,极大的保护了用户的隐私。但是如果我是用一个构造器来破解他呢,即为在类中构建一个构造器来修改对象的属性,面对这种情况下的解决方法是,我们可以将这些get和set方法写在构造器当中,就可以实现保护的功能。
比如这样就会失效
package test.encapsulation;
public class enca {public static void main(String[] args) {person smith = new person("smith", 5);String b=smith.getName();System.out.println(b);}
}
class person {String name;private int age;public person() {}public person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
继承
继承的含义以及为什么需要继承的存在
在写程序时,如果有两个类,比如说小学生和大学生,这两个类当中有许多属性和方法都是相同的,重复写会显得繁琐,这时候就可以使用继承,来使得程序简化。
当多个类中存在相同的属性和方法时,可以从这些类中抽象出父类,只需要通过extends来声明继承父类即可。
基本语法
class 子类 extends 父类{}
- 子类会自动拥有父类定义的属性和方法
- 父类又叫超类,基类。
- 子类又叫派生类。
快速入门案例
学生考试案例,大学生和小学生有姓名,考试成绩,考试内容三个属性,使用继承来简化代码。
package test.encapsulation;public class student {public static void main(String[] args) {DAStuednt daStuednt = new DAStuednt();daStuednt.name="mike";daStuednt.chanegji=80;XIAOStuednt xiaoStuednt = new XIAOStuednt();xiaoStuednt.name="johb";xiaoStuednt.chanegji=50;daStuednt.Infor();xiaoStuednt.Infor();}
}
class Student{String name;double chanegji;String kemu;public void Infor(){System.out.println("mame="+name+"chanegji"+chanegji+"kemu"+kemu);}
}
class DAStuednt extends Student{String kemu="gao shu";}
class XIAOStuednt extends Student{String kemu="shu xue";
}
好处
- 代码的复用性提高了
- 代码的扩展性和维护姓提高了
细节深入
-
子类继承了父类所有的方法和属性,但是私有属性和方法不能在子类中直接访问,要通过公共的方法去访问。
比如一个父类中的name属性为私有属性,我们可以在父类中用一个getname的方法返回name的值,通过调用父类的getname方法从而使子类可以使用父类的name属性。相当于一个中转站。 -
子类必须调用父类的构造器,完成父类的初始化。
当创造子类对象时,子类的无参构造器被调用,父类同时也被调用,并且先调用父类的无参构造器,后调用父类的无参构造器。
因为这其中隐藏了一个super()方法。eg:public class sub extends base{public sub(){super();………………}}
这个super()方法就是默认的。
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中使用super去指定使用父类的哪个无参构造器完成对父类的初始化工作,否则,编译不会通过。
下面我用代码帮助来理解public class base{public base(String name) {}} public class sub extends base{public sub(){super("mike");} }
就是必须在子类构造器中用super方法指定父类构造器,super括号里面写父类构造器的参数。
-
如果希望去指定调用父类的某个构造器,则显示的调用一下。
就是使用super来指定。 -
super在使用时,需要放在构造器的第一行。
-
super和this都只能放在构造器的第一行,因此这个两个方法不能共存在一个构造器内。
-
使用cutle +h 可以看到所有类的继承关系。
-
父类构造器的调用不限于直接父类!将一直往上追溯到顶级父类。
-
java时单继承机制,即为a不能继承b,再继承c。
-
不能滥用继承,子类和父类必须满足逻辑关系。
继承的本质
class GrandPa{String name= "大头爷爷";String hobby="";
}
class Father extends GrandPa{String name="大头爸爸";int age=39;
}
class Son extends Father{String name="大头儿子";
}
Son.son=new Son();
下面我提出三给个问题:
- son.name=?
- son.age=?
- son.hobby=?
我用内存分配来加以理解,代码由创建son对象来开始,对象创建后,会在方法区开辟空间,son的父类为father ,father的父类为grandpa,grandpa没有父类,因此方法区会先开辟grandpa的空间接下来是father最后是son。在堆中开始分配常量空间,会先为grandpa类开辟一个空间来储存常量一个是name,一个是hobby,接下来为father开辟空间进行常量分配,并会不进行覆盖或者冲突。
如果son .属性 这个属性没有在son中定义,就会到son的上一级去找直至找到,但是如果son有这个属性,则会直接输出。如果是父类的私有属性,则不能访问,会报错。如果父类的age是私有的,爷爷类也有age,他不会去输出爷爷的age,而是报错,因为father类的age是私有的,会直接报错,并不会去grandfather查找。
例题
编写Computer类,包含CPU,内存,硬盘等属性,getDetails方法用于返回Computer的详细信息。
编写PC类,继承Computer类,添加特有属性“品牌brand”
编写NotePad子类,继承Computer类,并添加特有属性“演示color”
编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息。
这里我用PC类作为例子。
super ()方法
基本介绍
super代表父类的引用,用于访问父类的属性,方法,构造器
基本语法
- 访问父类的属性,但不能访问父类的private属性
super.属性 - 访问父类的方法,不能访问父类的private方法
super.方法名(参数列表) - 访问父类的构造器
super(参数列表)注意这句只能放在构造器的第一句,只能出现一句。
super给编程带来的便利(细节)
-
调用父类构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
-
当子类中有和父类的属性或方法重名时,为了访问父类的成员属性,必须通过super。如果没有重名,使用super,this,直接访问是一样的效果。
eg:class A{public void say(){System.out.println("A say~~")} } class B extends A{public void test(){say();this.say();super.say();} }
第8 9 10 行都可以调用想要的方法,但是如果有重名的时候出现,他会直接调用本类的,因为在查找方法时,会先找本类,如果没有再去找父类的,当重名情况出现时就得使用super关键字了。this关键字此时是指代本类的,加不加都一样。
super.say()是直接找父类的,跳过了找本类的这一步。接下来的步骤和找本类的一样进行。
-
super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员,如果多个基类中都有同名的成员,使用super访问遵循就近原则。
方法重写/覆盖(override)
基本介绍
方法覆盖就是子类有一个方法,和父类的某个方法的名称,返回类型,参数一样,那么我们说子类的这个方法覆盖了父类的那个方法
我以一段代码加以理解
class A{public void say(){System.out.println("A")}
}
calss B extends A{public void say(){System.out.println("B");}
}
这里的say方法就构成了方法重写
注意事项
-
子类方法的参数,方法名称,要和父类方法的参数,方法名称完全一样
-
子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型,是子类返回类型的父类。
public Object getIfor() public String getInfor()
这里的object类型是string的子类,因此可以正常运行。
-
子类方法不能缩小父类方法访问的权限。
即为必须遵守public prote 空private的顺序。void say() private void say()
第一行的访问权限大于第二行的访问权限,所以第一行可以成为第二行的父类,但是第二行不能成为第一行的父类。
练习
编写一个person类,包括属性name age 构造器,方法say(返回自我介绍的字符串)
编写一个student类,继承person类,增加id,score的属性,以及构造器,定义say()方法返回自我介绍的信息。
在main方法中分别创建person和student对象,调用say方法输出自我介绍。
多态
为何提出多态
我用一个案例来来说明,编写一个程序,Master中有一个feed方法,可以完成主人给动物喂食的信息,Master主人类 Food食物类(fish鱼肉类,Bone骨头类,Rice米饭类)Animal动物类(Cat猫类 Dog狗类 Pig猪类)
当养的动物开始增多时,我们会发现main方法的feed方法会越写越多,这样的情况就需要多态出马来解决了。
基本介绍
方法或对象有多种形态,是面向对象的第三大特征,多态是建立在封装和继承的基础之上的
具体体现
-
方法的多态,重写和重载就体现出了多态,一个对象通过改变他的形参列表,可以去改变调用的方法。方法形成了重载,具有多种不同的形态。
-
对象的多态
-
一个对象的编译对象和运行类型可以不一致。
-
编译类型在定义对象时,就确定了,不能改变。
-
运行类型是可以变化的。
-
编译类型看定义时=的左边,运行类型看=的右边。
class Animal{} class Dog extends Animal{} Animal animal = new Dog();
注意看第七行,animal的编译类型时Animal但是他的运行类型是Dog。
如果我在第七行后写上一句
animal=new Cat();
此时的运行类型就是Cat,对象的类型进行了改变。这就被称为**对象的多态**。
-
案例
以动物喂食为案例,对上面的代码进行优化
只需要修改第一张图片即可
Animal可以指向Animal的所有子类对象,Food可以指向Food的所以子类对象。
注意事项和细节讨论
编译javac 运行java
-
前提条件:两个包或者类有着继承关系。
-
多态的向上转型
- 本质:父类的引用指向了子类的对象,比如上面的animal指向了dog
- 语法:父类类型 引用名 = new 子类类型()
- 特点
-
编译类型看左边,运行类型看右边
-
可以调用父类中的所有成员。(须遵守访问权限)
-
不能调用子类中的特有成员。
-
最终运行效果看子类的具体实现。
即调用方法时,从子类开始找,没有了找父类。
-
- 多态的向下转型
-
语法:子类类型 引用名 =(子类类型)父类引用;
-
只能强转父类的引用,不能强转父类的对象。
-
要求父类的引用必须指向是当前目标类型的对象。
-
可以调用子类类型的所有成员。
-
例子
希望调用cat的catchmouse方法class Cat extends Animal{public void catchmouse(){System.out.println("get mouse");}}Cat cat=(Cat) animal;cat.catchmouse();
Cat cat=(cat) animal; Dog dog=(Dog) animal;
第二个代码块的第二行会出错(类错误),因animal是指向猫的对象。第二行将其强制转换为狗,出现了错误。
-
属性没有重写之说,属性的值看编译类型。
class Base{int count = 10; } class Sub extends Base{int count =20; } Base base =new Sub(); base.count;
这里的第八行的值是10,因为他的编译类型为base,会直接从base中的对象开始查找,找i到后直接输出。
左边是编译类型 -
instanceOf 比较操作符
用于判断对象的类型是否为XXX类型或者XXX类型的子类。class A{} class extends B{} class c{} B b=new B(); C c=new C(); C a=new b(); System.out.println(b instanceof A); System.out.println(c instanceof A); System.out.println(a instanceof A);
第一个返回true,第二个返回false,第三个返回true。综合第一个和第三个来看,这个方法比较的是运行类型,而不是编译类型。
练习
写出下面代码的输出结果。
class Base{int count =10;public void display(){System.out.println(this.count);}
}
class Sub extends Base{int count=20;public void display(){System.out.println(this.count);}
}
Sub s=new Sub();
System.out.println(s.count);
s.display();
Base b=s;
System.out.println(b==s);
System.out.println(b.count);
b.display;
分析
首先第十三行创建了Sub的对象s,所以s所使用的属性都是Sub中的。
然后将b指向了s,实现了向上转型,b和s共同指向同一个地址,即为Sub的地址。
此时的b的编译类型为Base 运行类型为Sub。
答案
20 20 true 10 20
这个10和20,是因为程序运行过程中,属性看编译类型,方法看运行类型。
应用
多态数组
数组的定义类型为父类类型,里面实际保存的类型为子类类型
实际应用
现有一个继承结构,Student和Teacher为Person的子类。现要求创建一个Person对象和两个Teacher对象,统一放在数组中,并且调用每个对象的say方法。
多态参数
方法定义的形参为父类类型,实参类型允许为子类类型
应用实例
定义员工类Employee,包含姓名和月工资,以及计算年工资getAnnual的方法,普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工多了work方法,普通员工和经理类要求重写agtAnnual方法
最后text类中的getmoney方法中的参数就为多态参数,可以改变运行参数从而调用想要的方法。
Java的动态绑定机制
特点
- 当调用对象方法的时候,该方法会和该对象的内存地址/与运行类型绑定。
- 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。
实例
class A{public int i=10;public int sum(){return get()+10;}public int sum1(){return i+10;}public int get1(){return i;}
}
class B extends A{public int i=20;public int sum(){return i+20;}public int get(){return i;}public int sum1(){return i+10;}
}
main方法中
A a=new B();
System.out.println(a.sum());
System.out.println(a.sum1());
首先将a指向B类,实现向上转型,此时a的运行类型为B,编译类型为A。第一个输出语句中调用方法,看运行类型,所以输出的是40,第二个同理输出30。
但是我现在如果将B类中的sum和sum1方法注销,这两句的输出又是什么呢?
将B类的方法注销后,找不到方法,会去父类A查找,此时调用了A的sum方法,sum方法中又有get方法,那么此时到底是调用A中的方法还是B的方法呢?
根据Java的动态绑定机制,当调用对象方法的时候,该方法会和该对象的内存地址/与运行类型绑定,此时对象的运行类型为B,会和B类的get方法绑定,因此调用了B类中的get方法,输出了30。
运行sum1方法时,因为B被注销,无法查找到,会去A进行查找,调用a的sum1方法,此时的i根据Java的动态绑定机制第二条当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。这里的就是A中的i,所以输出了20。
Object类详解
Object类再java的lang包中,是自动引用的,程序中的所有对象都可以调用这个类中的方法
equals方法
equals和==的对比
- ==:既可以判断基本类型,又可以判断引用类型
- 如果判断基本类型,判断是值是否相等。
- 如果判断引用类型,判断的是地址是否相等,即为判断是不是同一个对象。
Java中的方法
Scanner方法
-
导入该方法库
- import Java.util.Scanner;
-
新建一个对象,来接收这个方法
-
使用到new()
-
Scanner A=new Scanner(System.in)
-
-
看需要从键盘输入哪些类型,来确定下面的方法,我用int为例子
- int a=A.nextInt();
-
接下来从键盘输入即可。
-
字符串输入时使用A.next()方法
随机方法(Math.random)
math库中的random方法,这个方法可以返回0.0到1.0之间的double类型数字。
eg:如果我想随机0~100之间的随机数字
int a=Math.random()*100+1
写出上面的代码即可
标签的使用(label)通常和break和continue一起使用。
- 标签的名称由程序员定义。
- 标签在实际开发中尽量不要使用。
- break后的标签是什么,就退回到那个标签后的循环体。
- 如果break后面没有指定,就退出到最近的循环体。
string中的equals方法(比较字符串是否相同)
用于将字符串进行比较,返回boll值。
比如
- String name="mike";System.out.print(name.equals("nike")); -
这个程序执行完后会返回一个false值。
计算数组长度的方法(length方法)
使用数组名称.length可以得到数组的长度
hashCode()方法【可以查询地址】
使用这个该方法,可以返回一个哈希码值,这个哈希码是根据该对象的地址进行变化得出的,可以变相的进行地址的比较。
排序
分类
- 内部排序
- 指将需要处理的所有数据都加载到内部储存器进行排序,包括交换式排序法,选择式排序法和插入式排序法。
- 外部排序
- 指数据量过大,无法全部加载到内存中,需要借助外部储存进行排序,包括合并排序法和直接合并排序法。
冒泡排序
进行多轮比较,将大数放到小数的后面。 (进行数字数目减一次)
第一轮比较会将最大的数字移动到最后一位
第二轮比较会将第二大的数组移动到倒数第二位,在比较时,因为第一大的数已经确定下来了,所以没有和最后一个比较的必要
综上所述,比较的轮数是数字数目总和减一
public class MaoPao {public static void main(String[] args) {int a[] = {5, 8, 4, 9, 55, 47, 1, 0};for (int j = 0; j < a.length - 1; j++) {for (int i = 0; i < a.length - 1- j; i++) {int temp = 0;if (a[i] > a[i + 1]) {temp = a[i + 1];a[i + 1] = a[i];a[i] = temp;}}}for (int s = 0; s < a.length; s++) {System.out.print(a[s]+" ");}}
}
,看运行类型,所以输出的是40,第二个同理输出30。
但是我现在如果将B类中的sum和sum1方法注销,这两句的输出又是什么呢?
将B类的方法注销后,找不到方法,会去父类A查找,此时调用了A的sum方法,sum方法中又有get方法,那么此时到底是调用A中的方法还是B的方法呢?
根据Java的动态绑定机制,当调用对象方法的时候,该方法会和该对象的内存地址/与运行类型绑定,此时对象的运行类型为B,会和B类的get方法绑定,因此调用了B类中的get方法,输出了30。
运行sum1方法时,因为B被注销,无法查找到,会去A进行查找,调用a的sum1方法,此时的i根据Java的动态绑定机制第二条当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。这里的就是A中的i,所以输出了20。
Object类详解
Object类再java的lang包中,是自动引用的,程序中的所有对象都可以调用这个类中的方法
equals方法
equals和==的对比
- ==:既可以判断基本类型,又可以判断引用类型
- 如果判断基本类型,判断是值是否相等。
- 如果判断引用类型,判断的是地址是否相等,即为判断是不是同一个对象。
Java中的方法
Scanner方法
-
导入该方法库
- import Java.util.Scanner;
-
新建一个对象,来接收这个方法
-
使用到new()
-
Scanner A=new Scanner(System.in)
-
-
看需要从键盘输入哪些类型,来确定下面的方法,我用int为例子
- int a=A.nextInt();
-
接下来从键盘输入即可。
-
字符串输入时使用A.next()方法
随机方法(Math.random)
math库中的random方法,这个方法可以返回0.0到1.0之间的double类型数字。
eg:如果我想随机0~100之间的随机数字
int a=Math.random()*100+1
写出上面的代码即可
标签的使用(label)通常和break和continue一起使用。
- 标签的名称由程序员定义。
- 标签在实际开发中尽量不要使用。
- break后的标签是什么,就退回到那个标签后的循环体。
- 如果break后面没有指定,就退出到最近的循环体。
string中的equals方法(比较字符串是否相同)
用于将字符串进行比较,返回boll值。
比如
- String name="mike";System.out.print(name.equals("nike")); -
这个程序执行完后会返回一个false值。
计算数组长度的方法(length方法)
使用数组名称.length可以得到数组的长度
hashCode()方法【可以查询地址】
使用这个该方法,可以返回一个哈希码值,这个哈希码是根据该对象的地址进行变化得出的,可以变相的进行地址的比较。
排序
分类
- 内部排序
- 指将需要处理的所有数据都加载到内部储存器进行排序,包括交换式排序法,选择式排序法和插入式排序法。
- 外部排序
- 指数据量过大,无法全部加载到内存中,需要借助外部储存进行排序,包括合并排序法和直接合并排序法。
冒泡排序
进行多轮比较,将大数放到小数的后面。 (进行数字数目减一次)
第一轮比较会将最大的数字移动到最后一位
第二轮比较会将第二大的数组移动到倒数第二位,在比较时,因为第一大的数已经确定下来了,所以没有和最后一个比较的必要
综上所述,比较的轮数是数字数目总和减一
public class MaoPao {public static void main(String[] args) {int a[] = {5, 8, 4, 9, 55, 47, 1, 0};for (int j = 0; j < a.length - 1; j++) {for (int i = 0; i < a.length - 1- j; i++) {int temp = 0;if (a[i] > a[i + 1]) {temp = a[i + 1];a[i + 1] = a[i];a[i] = temp;}}}for (int s = 0; s < a.length; s++) {System.out.print(a[s]+" ");}}
}
## 面向对象编程高级部分### 类变量和类方法#### 类变量##### 解决问题(快速入门)现在由一群小孩玩游戏,不断有新的小孩加入,问现在有多少小孩在玩游戏。如果使用现有的技术来解决这个问题,我们可以使用count++来获得值,代码实现如下:~~~java
package static_;public class Children_game {public static void main(String[] args) {int count=0;children child1=new children("mike");count ++;children child2=new children("jhon");count ++;children child3=new children("zhu");count ++;System.out.println("the number is "+count);}public static class children{private String name;public children(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
}
注意:当写children方法时,要给前面加上static关键字,因为main是一个static方法,class Clerk是一个非静态的内部类,只能被该类的非静态方法访问。否则会报错。
静态方法只能调用静态类和方法
问题分析:
- count是一个独立于对象和类的变量,很尴尬。
- 以后我们访问count很麻烦,没有使用到OOP。
- 因此我们引入 类变量、静态变量。
想法:我们设计count时,可以让在这个对象被创建时,就加一,并且count是所有对象共享的。
使用类变量进行代码实现
package static_;public class childrengame {public static void main(String[] args) {children c1=new children("5");children c2=new children("2");children c3=new children("6");System.out.println(c1.count);System.out.println(c2.count);System.out.println(c3.count);}public static class children{private String name;public static int count =0;public children(String name) {this.name = name;count ++;}public String getName() {return name;}public void setName(String name) {this.name = name;}public static int getCount() {return count;}public static void setCount(int count) {children.count = count;}}
}
这里的输出全为3,因为所有的对象都使用这一个变量。是共享的。
定义以及概念
概念:
类变量也叫静态变量和静态属性,是该类所有对象共享的变量,任何一个对象去访问时,取到的都是相同的值。
如何定义
访问修饰符 static 数据类型 变量名;
static 访问修饰符 数据类型 变量名;
如何访问
类名.类变量名
对象名.类变量名
使用细节和注意事项
什么时候需要使用类变量
在当需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量。
类变量和实例变量的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
细节和注意
- 加上static称为类变量或者静态变量否则称为实例变量/普通变量/非静态变量
- 实例变量不能通过类名.类变量名的方式访问。
- 类变量在类加载时就会初始化,几时没有创建对象,在类加载时就可以使用类变量了
- 类变量的生命周期是随着类进行变化的。
类方法
介绍
类方法也叫静态方法
形式:
访问修饰符 static 数据返回类型 方法名(){}
static 访问修饰符 数据返回类型 方法名(){}
调用方法:
类名.类方法名 或者 对象名.类方法名
应用案例
统计学费总和,使用代码实现
package static_;
public class totla_money {public static void main(String[] args) {student s1=new student("make");student.payfee(45);student tom = new student("tom");student.payfee(50);student.showFee();}
}
class student {private String name;private static double Money = 0;public student(String name) {this.name = name;}public static double payfee(double Money) {student.Money += Money;return Money;}public static void showFee() {System.out.println("tolal is "+student.payfee(student.Money));}
}
使用场景
当方法中不涉及任何和对象相关的成员时,可以使用类方法进行开发,提高代码开发效率。
使用细节
- 类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区。
- 类方法中没有this的参数,普通方法中含有this的参数,因为类方法是所有的对象都可以调用的方法,this是单个的对象调用的。
- 类方法可以通过类名调用,也可以通过对象名调用。
- 类方法中不允许使用和对象有关的关键字
- 类方法中,只能访问静态变量或者静态方法
- 普通成员方法,即可以访问非静态成员也可以访问静态成员
深入理解main方法
main方法的语法
- main方法是由java虚拟机调用的,所以只能是public
- 调用main方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令传递给所执行的类的参数,接收参数
- args数组就是接收参数的
特别提示
-
在main方法中,我们可以直接调用main方法所在类的静态方法或者属性,但是不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
举例说明package mian; public class main01 {private static String name ="make";static void pp(){System.out.println("hi");}public void pp1(){System.out.println("hello");}public static void main(String[] args) {System.out.println("name is "+name);pp();main01 main01 = new main01();main01.pp1();} }
其中的name和pp()都是静态成员,main方法是静态方法,所以可以调用name和pp()。但是pp()1不是静态方法,所以得先建立一个对象才能对他进行调用。
案例演示
package mian;
public class CommandPara {public static void main(String[] args) {for(int i=0;i< args.length;i++){System.out.println("args["+i+"]="+args[i]);}}
}
在idle中这样给args传入数据
最后就会成功输出
代码块
基本介绍
代码块又称为初始化块,属于类中成员,类似于类方法,将逻辑语句封装在方法体中,通过{}包装起来。
但是和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法
【修饰符】{代码
};
注意:
- 修饰符可选,要写只能写static
- 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的叫普通代码块
- 逻辑语句可为任何逻辑语句比如输出,输入,循环,判断,方法调用等
- ;可写可不写
应用场景和案例演示
应用场景:
- 相当于另外一种形式的构造器,可以做初始化的操作,如果多个构造器中都又重复的语句,可以抽取到代码块中,提高代码的重用性。
案例演示:
package mian;public class CodeBlock {public static void main(String[] args) {Movie movie = new Movie("mike",556);System.out.println("is ok");}}
class Movie{{System.out.println("start moive");System.out.println("the moive is over");}private String name;private double price;private String director;public Movie(String name) {this.name = name;}public Movie(String name, double price) {this.name = name;this.price = price;}public Movie(String name, double price, String director) {this.name = name;this.price = price;this.director = director;}
}
这里中的电影开始和结束是必须输出的语句,所以把他提取出来放入代码块,当创建这个对象时,代码块会自动运行,输出中的moive start…………是在is ok之前的。所以当程序运行时,代码块先进行运行。
注意事项
-
static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且之后执行一次,如果是普通代码块,每创建一个对象,就只执行一次。
-
类什么时候加载
- 创建对象实例的时候
- 创建子类对象实例的时候,父类也会被加载
- 使用类的静态成员时
-
普通的代码块,在创建对象实例的时候,会被隐式的调用,创建一次调用一次。
如果是使用类的静态成员时,普通代码块并不会执行。 -
当创建一个对象在一个类当中,调用顺序
调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和静态变量初始化,则按照他们定义的顺序调用)
调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和普通变量初始化,则按照他们定义的顺序调用)
- 构造器前面其实隐藏了一个super()和调用普通代码块,新写一个类演示说明(如下),静态相关的代码块,属性初始化,在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的。
class A(){public A(){super();System.out.println("ok");}
}
这个问题可以在代码块中使用this来体现,this的任何属性都为空,因此this是在构造器之前运行的
运行的顺序:第一步 进行父类的普通代码块运行,父类再找他的父类,如果运行完了,进入第二步,运行自己的代码块,最后进行第三步运行自己类中的程序。
- 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按照定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按照定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按照定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法
创建演示
package mian;public class CodeBlock001 {public static void main(String[] args) {new BBB();}}
class AAA{private static int n1 =getValue01();static{System.out.println("A的一个静态代码块。。。");}{System.out.println("A的一个普通代码块。。。");}public int n2=getVlaue02();public static int getValue01(){System.out.println("getValue01");return 10;}public int getVlaue02(){System.out.println("getValue02");return 10;}public void A(){System.out.println("A的构造器");}
}
class BBB extends AAA {private static int n3=getValue03();static {System.out.println("这是b的一个静态代码块。。。");}{System.out.println("这是b的一个普通代码块。。。");}public static int getValue03(){System.out.println("getValue03");return 10;}public void B(){System.out.println("这是b的构造器");}
}
这是输出结果
getValue01
A的一个静态代码块。。。
getValue03
这是b的一个静态代码块。。。
A的一个普通代码块。。。
getValue02
这是b的一个普通代码块。。。
这体现出,先执行静态,再进行普通,顺序是先父类再子类。
设计模式
单例设计模式
什么是单例模式
所谓类的单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式的方式
饿汉式
-
构造器私有化
-
在类的内部创建
-
向外暴露一个静态的公共方法
-
例子(代码实现)
package mian;public class SingleTon1 {public static void main(String[] args) {GirlFriend xx= GirlFriend.getInstance();System.out.println(xx);} } class GirlFriend {private String name;private static GirlFriend gf = new GirlFriend("11");private GirlFriend(String name) {this.name = name;}public static GirlFriend getInstance(){return gf;}@Overridepublic String toString() {return "GirlFriend{" +"name='" + name + '\'' +'}';} }
我接下来解释这段代码,首先创建了私有的属性,然后创建了一个私有的对象,我们现在需要将这个对象传出去,所以有 public static GirlFriend getInstance()这个方法,因为只有静态方法可以调用静态属性,所以这里的方法是静态方法,接着getInstance是将对象实例化的方法,就是这个对象是抽象存在的,现在要调用其对象,就得将它实例化使用,接着返回出其实例化后的对象。这一步就是向外暴露一个静态的方法。
懒汉式
-
构造器私有化
-
类的内部创建对象
-
向外暴露一个静态的公共方法
-
代码实现
package mian;public class SingleTon1 {public static void main(String[] args) {Cat xixi =Cat.getInstance();System.out.println(xixi);} } class Cat{private String name;public Cat(String name) {this.name = name;}private static Cat cat;public static Cat getInstance(){if(cat==null){Cat xixi = new Cat("xixi");return xixi;}else{return cat;}}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\'' +'}';} }
下面我来说明这个懒汉模式,懒汉模式是单例模式的一种,他是在调用这个方法的时候来创建对象的,并且这个对象仅仅创建一次,第二次调用的时候,依然返回第一次调用他时创建的对象。
区别
他们两个的区别在于:
- 饿汉式是在类加载的时候就创建了对象,而懒汉式是在使用的时候才会创建。
- 饿汉式不存在线程安全问题,但是懒汉式存在。
- 饿汉式存在着浪费资源的可能,但是懒汉式不存在。
final关键字
应用场景
- 当不希望类被继承的时候,可以使用final来修饰
- 当不希望父类的某个方法被子类覆盖/重写的时候,就可以使用final关键字
- 当不希望类的某个属性被修改,可以使用final修饰
- 当不希望某个局部变了被修改,可以使用final修饰
注意事项和细节讨论
-
final修饰的属性又叫常量,一般用XX_XX_XXX来命名
-
final修饰的属性在定义的时候,必须要赋初值,并且以后不能修改,赋值可以如下位置之一
- 定义的时候:public final double TAX_XRATE=0.08;
- 在构造器当中
- 在代码块当中
-
如果final修饰的属性是静态的,则初始化的位置只能是:
- 定义时
- 在静态代码块,不能在构造器中赋值
-
final不能被继承,但是可以实例化对象
-
如果类不是final类,但是含有finla方法,则该方法不能被重写,但是可以被继承
final相当于c语言当中的conest
抽象类
应用场景
父类方法不确定的时候使用,考虑将这个方法变为抽象方法,所谓抽象方法就是没有实现的方法即为没有方法体,当一个类中存在抽象方法的时候,需要将这个类声明为abstract类,将来这个抽象类的作用是,他会被子类继承,由抽象子类实现这个抽象方法。
介绍
-
使用abstract修饰一个类的时候,他就是抽象类
访问修饰符 abstract 类名{}
-
使用abstract关键字来修饰一个方法的时候,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表){}没有方法体
-
抽象类的价值更多在于设计,是让设计者设计好后,让子类继承并且实现抽象类()
-
抽象类在框架和设计模式使用的比较多
细节
-
抽象类不能被实例化
-
抽象类可以不包含抽象方法。
-
一旦含有抽象方法,这个类就必须声明为abstract。
-
abstract只能修饰类和方法,不能修饰其他和属性。
-
抽象类可以有任意成员
-
抽象方法不能有主题,即不能实现
-
如果一个类继承了抽象类,则他必须实现抽象类的所有的抽象方法,除非他自己也声明成为抽象类。
-
抽象方法不能使用private final static 来修饰
模板使用模式
需求
-
有多个类,完成不同任务的job
-
要求统计得到各自完成任务的时间
我先使用最基本的方式来写
package Abstract;public class A {public static void job(){long start =System.currentTimeMillis();int sum=0;for(int i=1;i<=800000;i++){sum =sum +i;}long end=System.currentTimeMillis();long oo=end-start;System.out.println(oo);} } package Abstract;public class B {public static void job(){long start =System.currentTimeMillis();int sum=0;for(int i=1;i<=80000000;i++){sum =sum +i;}long end=System.currentTimeMillis();long oo=end-start;System.out.println(oo);} } package Abstract;public class tex {public static void main(String[] args) {A aa = new A();aa.job();} }
我们可以发现这两个类中存在相同的计算模式和程序,我们可以将它提炼出来形成一个模板,将它变成为抽象父类,让子类来实现自己想要的东西。
package Abstract;abstract class Template {public abstract void code();public void caleTimes(){long a=System.currentTimeMillis();code();long b=System.currentTimeMillis();long c=b-a;System.out.println("耗费时间"+c);} } package Abstract;public class A extends Template {@Overridepublic void code() {int sum=0;for(int i=1;i<=80000000;i++){sum =sum +i;}} } package Abstract;public class B extends Template{@Overridepublic void code() {int sum=0;for(int i=1;i<=800000;i++){sum =sum +i;}} }package Abstract;public class tex {public static void main(String[] args) {A aa = new A();aa.caleTimes();B bb= new B();bb.caleTimes();} }
接口
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类将要使用的时候,再根据具体情况把这些情况写出来
语法:
interface 接口名{
属性;
方法;
}
class 类名 implements 接口{
自己的属性;
自己实现;
必须实现的接口的抽象方法
}
小结:
- 在jdk7.0前接口里的所有方法都没有方法体
- jdk8.0后接口类可以有静态方法,默认方法,也就是接口中可以有方法的具体实现。
package interface111;public interface Interface01 {public int n1 =10;public void hi();//在接口中,抽象方法,可以省略abstract关键字default public void ok(){System.out.println("ok");}
}
package interface111;public interface Interface02 {public static void main(String[] args) {A a = new A();a.hi();a.ok();}
}
class A implements Interface01{@Overridepublic void hi() {System.out.println("is Interface02");}
}package interface111;public interface Interface02 {public static void main(String[] args) {A a = new A();a.hi();a.ok();}
}
class A implements Interface01{@Overridepublic void hi() {System.out.println("is Interface02");}
}
下面的这个A类,就使用了接口,接着将接口中的抽象方法实现,在进行对象实例化调用。
深入讨论
接口可以规范程序员的程序规范,使得项目有条不紊的开发。
注意事项和细节
- 接口不能被实例化。
- 接口中所有的方法是public方法,接口中的抽象可以不用abstract修饰。
- 一个普通类实现接口,就必须将该接口的所有方法实现。
- 抽象类实现接口,可以不适应实现接口的方法。
- 快捷键:alt加enter,可以全选抽象方法进行实现,将光标停在implements上
- 一个类可以同时实现多个接口,接口之间使用逗号隔开(但是继承只能继承一种父类)
- 接口中的属性只能是final,而且是public static 的
int n1 =10;====》final int n1=10;相互等价 - 接口中属性的访问是,接口.属性名
- 接口不能继承其他类型的类,但是可以继承其他的多个接口
interface A extends B,C{} - 接口中的修饰符只能是public和默认。
接口的多态特性
-
多态参数,既可以接受手机对象,也可以接受相机对象,实现了接口的参数多态
-
多态数组
package interface111; public interface Interface02 {public static void main(String[] args) {Usb usbs[]=new Usb[2];usbs[0]=new Phone();usbs[1]=new Camera();for (int i=0;i< usbs.length;i++){usbs[i].work();}} } interface Usb{void work(); }class Phone implements Usb{public void cla(){System.out.println("yes");}@Overridepublic void work() {System.out.println("phone");cla();}}; class Camera implements Usb{@Overridepublic void work() {System.out.println("camera");} };
-
接口存在多态传递的现象
比如说接口A继承了接口B,然后类C实现了接口A的对象实例,因为a是继承于b的,所以c实现的实例对象是要对b负责的,要完成b的抽象方法。
四种内部类
基本介绍
一个类的内部又完成嵌套了另一个类的结构,被嵌套的类称为内部类,嵌套其他类的类称之为外部类,使我们类的第五大成员,内部类的最大特点就是可以直接访问私有属性,并且可以提现类与类之间的包含关系
基本语法
class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}
分类
- 定义在外部类局部位置上(比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名)
- 定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类
说明
- 局部内部类是定义在外部类的局部位置上,比如方法中,并且有类名。
- 不能添加访问修饰符,因为他的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量可以使用final
- 可以直接访问外部类的所有成员,包含私有的。
- 作用域:仅仅在定义他的方法或代码块中。
- 外部类——访问——->外部类的成员【访问方式:直接访问】
- 外部类——访问——->局部内部类的成员.
访问方式:创建对象,再访问(注意:必须在作用域内访问) - 外部其他类—–不能访问——>局部内部类的成员(因为局部内部类是一个局部变量)、
- 如果外部类和局部内部类存在方法属性重名,默认遵循就近原则,如果想访问外部类的成员,则可以使用外部类名.this.成员去表示。
匿名内部类的使用
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
- 本质是类
- 内部类
- 该类没有名字
- 同时还是一个对象
基本语法
new 类或接口(参数列表){类体
};
案例(基于接口的)
package INeerclass;public class Anony {public static void main(String[] args) {Outer outer = new Outer();outer.method();A tiger=new A(){@Overridepublic void cry() {System.out.println("656565");}};tiger.cry();}
}
class Outer{private int i =10;public void method(){//基于接口的匿名内部类// 1.需求:想使用a接口,并且创建对象// 2.传统方式:写一个类,实现该接口,并且创建对象Tiger tiger = new Tiger();tiger.cry();//需求:tiger使用一次,后面不再使用//所以创建一个tiger类太浪费//解决方案:使用匿名内部类简化开发:}
}
interface A{public void cry();
}
class Father{String name;public Father(String name){this.name=name;}public void test(){}
}
class Tiger implements A{@Overridepublic void cry() {System.out.println("666666");}
}
这块tiger的编译类型是a,他的运行类型是这个匿名内部类
编译类型看定义时=的左边,运行类型看=的右边。
可以通过tiger.getClass();查看他的运行类型。
案例(基于类的)
package INeerclass;
public class Anony {public static void main(String[] args) {Father father=new Father("jack"){@Overridepublic void test() {System.out.println("888888");;}};father.test();System.out.println(father.getClass());}
}
class Father{String name;public Father(String name){this.name=name;}public void test(){System.out.println("7777");}
}
输出88888
匿名内部类重写了text方法,更加高效。
这个匿名类其实还是继承于原类的
使用场景
将匿名内部类当做实参传递
package INeerclass;public class wanda {public static void main(String[] args) {f1(new IAA() {@Overridepublic void show() {System.out.println("666");}});}public static void f1(IAA ia){ia.show();}
}
interface IAA{public void show();
}
这里首先写了一个接口,然后定义了一个方法,这个方法的形参是接口类型的,当调用这个方法的时候,直接创建一个新对象,这个对象即是接口也是匿名类,进行实现程序,简化代码。
题目:有一个铃声接口bell,里面有个ring方法,有一个手机类cellphone,有闹钟接口alarmclock,参数是bell类型,测试手机类的闹钟功能,打印666,再传入一个匿名内部类打印888
package INeerclass;public class CellPhone {public static void main(String[] args) {NoPhone noPhone = new NoPhone();noPhone.alarmclock(new Bell() {@Overridepublic void ring() {System.out.println("ring ring ring ...");}});noPhone.alarmclock(new Bell() {@Overridepublic void ring() {System.out.println("qichuangle");}});}
}class NoPhone{public void alarmclock(Bell bell){bell.ring();}
}
interface Bell{public void ring();
}
成员内部类
说明:将类写在类中,没有写在方法或者形参里,就是成员内部类
可以使用外部类的所有属性和方法
作用域:和外部其他成员相同,为整个类体
内部类访问外部类直接访问
外部类访问内部类先创建对象再访问
外部其他类使用内部类的三种方式
-
使用外部类new一个内部类的对象
class outer{class inner{} } psvm{outer O=new outer();outer.inner a =o.new inner(); }
相当于把new出的inner()当做一个outer的成员
-
在外部类中编写一个方法,返回一个内部类的实例。
遵循就近原则
可以通过外部类名.this.属性来访问外部类的所有成员
静态内部类
说明:将类写在类中,没有写在方法或者形参里,再用static修饰就是静态内部类
-
可以访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
-
可以添加任意访问修饰符
-
作用域是一整个类体
-
内部类访问外部类直接访问
-
外部类访问内部类先创建对象再访问
-
外部其他类访问内部静态类
-
new一个外部类名.静态内部类名
-
写一个方法,返回静态内部类的实例
-
-
遵循就近原则
-
如果想访问外部类的成员,直接外部类名.成员
枚举
介绍
案例引入
要求创建Season对象
package Chapt11.enum_;/*** @another SunHaoyu* @verson 1.0*/
public class enum01 {public static void main(String[] args) {Season season = new Season("spring", "warm");season.printt(season);}
}
class Season{private String name;private String desc;public Season(String name, String desc) {this.name = name;this.desc = desc;}public void printt(Season season){System.out.println(season.desc+" and "+season.name);}
}
这种设计方式,会使得任何人都可以给season类进行添加或者修改,会破坏这个类,但是对于季节来说,最多只有四个,是有限而且固定的,这种方式并不好。
那么我们现在希望使用另外一种方式来进行设计,此时便可以使用枚举类
说明
枚举是一种特殊的类,是一种常量的集合,里面只包含一组有限的特定的类。
自定义枚举类
package Chapt11.enum_;/*** @another SunHaoyu* @verson 1.0*/
public class enum01 {public static void main(String[] args) {}
}
class Season{private String name;private String desc;public static final Season Spring=new Season("Spring","warm");private Season(String name, String desc) {this.name = name;this.desc = desc;}public void printt(Season season){System.out.println(season.desc+" and "+season.name);}public String getName() {return name;}public String getDesc() {return desc;}}
分为三步完成:
- 将构造器私有化,使得不能在外部创建对象
- 将set方法删除,使得不能在外部更改内容
- 在Season内部直接创建固定的对象,最好使用final+static共同修饰,让他不能被更改。
关键字实现枚举类
使用enum实现
package Chapt11.enum_;/*** @another SunHaoyu* @verson 1.0*/
public class enum01 {public static void main(String[] args) {Season ss= Season.SPRING;ss.printt();}
}
enum Season{SPRING("Spring","warm"),WINTER("Wanter","Cold");private String name;private String desc;private Season(String name, String desc) {this.name = name;this.desc = desc;}public void printt(Season season){System.out.println(season.desc+" and "+season.name);}public String getName() {return name;}public String getDesc() {return desc;}}
-
将class用enum代替
-
接着将对象创建部分改变
-
如果想继续创建新的对象,使用逗号分割,最后一个对象使用分号进行结尾。
-
创建的新的对象,必须写在最前面
-
使用的时候
Season ss= Season.SPRING;ss.printt();
用第一行进行对象的建立,接着才能调用他的方法。
使用关键字实现枚举的注意事项
- 使用enum关键字来开发一个枚举类的时候,会默认继承一个Enum类
- 新建的对象写在enum类的第一行
直接调用对象,默认调用他的toString方法
使用枚举时常用的方法
因为继承了enum类,所以他的方法都可以使用
下面是几个常见的方法:
ToString方法
之前重写过了,用于返回类对象的各种信息
name方法
用于得到枚举对象的对象名称
ordinal方法
用于得到该枚举对象的次序,从0开始编号
values方法
会返回一个数组,是将所有的枚举对象封装起来返回出来
增强for循环
int []nums={1,2,3};
for(int i=0;i<nums.length;i++){System.out.println(i);
}
for(int i : nums){System.out.println(i);
}
下面这个是增强for循环,
是将数组中,依次拿一个出来赋给i,当数组结束的时候,for循环也结束了。
valueOf方法
这个方法是将字符串转换为枚举对象,要求字符串为已有的常量,不然会报错。其实是进行一个查找的功能,如果枚举对象中存在要找的字符串,会将和这个枚举对象返回出来。
compareTo方法
比较两个枚举常量,比较的是位置编号。
比较后,将前面的编号减去后面的编号,得到的结果返回
a.compareTo(b);
当a的编号是0,b是1,就会返回-1
课堂练习
声明week枚举类,其中包含星期一到星期天的定义,使用enum自带的方法输出这些信息。
package Chapt11.enum_;
/*** @another SunHaoyu* @verson 1.0*/
public class KnowWeek {public static void main(String[] args) {Week []weeks=Week.values();for (Week w:weeks){System.out.println(w.toString());}}
}
enum Week{Monday("星期一"),Tuesday("星期二"),Wednesday("星期三"),Thursday("星期四"),Friday("星期五"),Saturday("星期六"),Sunday("星期天");private String name;private Week(String name) {this.name = name;}@Overridepublic String toString() {return name ;}
}
注解Annotation/元数据Metadata
简单说明
- 注解用于修饰解释 包,类,方法,属性,构造器,局部变量等数据信息。
- 和注释不同,注解不影响程序逻辑,但注解可以被编译或者运行,相当于嵌入在代码中的补充信息。
- 在JavaSE中,注解使用的目的较为简单,用于标记过时的功能,忽略警告,在JavaEE中用于配置应用程序的任何切面,代替他在旧版中所遗留的繁冗的代码和XML配置。
基本的Annotation介绍
使用Annotation时要在前添加@符号,并且把他当做一个修饰符使用,用于修饰他支持的元素
三个基本的Annotation
- @Override:限定某个方法,是重写父类方法,该注解只能用于该方法。
toString方法就用了这个 - @Deprecated:用于表示某个程序元素已过时
- @SupperssWarnings:抑制编译器警告
@Override
使用说明
- @Override这个注解放在某个方法,表示子类的这个方法重写了父类的这个方法。
- 如果不写@Override,也是重写了父类的方法
- 但是,加上@Override,编译器会进入父类查找是不是有这个方法,如果父类没有这个方法,程序就会报错。
public @interface Override
这个表示是一个接口注解类
- 查看@Override的源码,发现@Target,其中的@Target是修饰注解的注解,称之为元注解。
@Deprecated
使用说明
- 用于表示某个程序元素已经过时
- 可以修饰方法,包,字段,类,参数等等
- @Deprecated可以做到新旧版本的过渡
- 被@Deprecated标记依然可以使用,只是不推荐使用
@SupperssWarnings
使用说明
- 比如说idle出现黄色警告,但是还是可以正常运行,我们不希望看到这些警告的时候,可以使用@SupperssWarnings进行警告的抑制,让他不出现
@SupperssWarnings({"…………"})
给里面填入相应的警告即可,比如填入all就可以抑制所有的警告。
- 这个@SupperssWarnings是和放的位置相关,放在方法上,就抑制方法里面的,放在类上,就是抑制类的
四种元注解
元注解本身作用不大,是用于修饰其他Annotation
种类:
- Retention 指定注解的作用范围,三种SOURCE,CLASS,RUNTIME
- Target 指定注解可以在哪些地方使用
- Documented 指定该注解是否会在javadoc中体现
- Inherited 子类会继承父类注解
异常 -Exception
概念
异常处理体现在,当是一个小问题,不影响整体程序的运行,但是会让程序报错,这样会使得程序中止运行,影响用户体验,在这个时候,我们就需要处理异常,来使得程序正常运行。
怎么操作
-
将改代码块选中,使用快捷键**alt ctrl t**,选择try+catch
-
如果进行异常处理,那么即使出现异常,程序依然可以继续执行
try{int res =1/0; }catch(Exception e){System.out.println(e.getMessage()); }
这个getmessage方法,可以输出异常的原因。
执行过程中发生的异常事件可以分为两类
- Error:java虚拟机无法解决的严重问题,比如jvm系统内部错误,资源耗尽等错误。
- Exception:其它因编程错误或者偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。
- Exception分为两类:1.程序运行的时候发生的异常2.编译过程中,软件发现的异常。
常见运行时异常五大类
NullPointerException 空指针异常
当应用程序试图在需要对象的时候使用null,抛出该异常
AirthmeticExption 数字运算异常
当出现运算过程数字错误,所引发的异常,比如2/0
ArrayIndexOutBoundsException 数组下标越界异常
使用非法索引访问数组时抛出的异常
ClassCastException 类型转换异常
class A{}
class B extends A{}
class C extends C{}
public static void main(String[] args){A b =new B();B b2=(B)b;C ca=(C)b;
}
这里的c和b没有任何的继承关系,所以会报出类型转换异常的错误,但是a和b有继承关系,第六行属于向下转型,是可以的,第五行创建的对象b,他的运行类型是B,编译类型是A,属于向上转型。
NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成为一种数值类型,但是字符串不能转换为适当类型时,抛出该异常。
这种异常可以确保我们输入满足条件的数字
异常处理
基本介绍
异常处理就是当异常发生时,对异常的处理方式。
处理方式
-
try-catch-finally
程序员在代码中捕获发生的异常,自行处理 -
throws
将发生的异常抛出,交给调用者处理,最顶级的处理者就是JVM-
示意图
-
try-catch-finally
try{代码中的可能异常 }catch(Exception e){当异常发生的时候,会将异常封装起来,传给Exception对象e,传递给catch得到异常对象之后,可以由程序员任意处理 }finally{不管try代码块是否有异常发生,始终要执行finally这个代码块,所以通常将释放资源的代码放在finally中执行 }
-
throws处理机制
throws是假如方法出现异常,他会将这个异常抛出,传给上一级方法,接着这个方法又将异常抛出,传给main方法,最后由main方法传给JVM处理器,由他来处理异常。
JVM会输出异常信息,接着中止程序运行并且退出。那我们想想,在日常中写程序,不写throws或者try-catch也可以报出异常中止程序,这是因为如果你不写这两个其中一种的时候,程序会在main方法前面默认添加throws关键字。
-
-
try-catch-finally方式处理异常说明
基本语法
try{可疑代码将异常生成异常对象,传给catch块
}catch(异常){对异常的处理
}
try{int res =1/0;
}catch(Exception e){System.out.println(e.getMessage());
}
- 异常如果没有发生,就不会进入catch块
- 异常如果发生了,就会进入catch块进行处理
- 当你想关闭连接或者释放资源的时候,可以将代码写入finally块当中
throws说明
基本介绍
- 在方法声明中使用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是他的父类。
使用的时候,我以下面的案例说明
public void f1(){int res =1/0;
}
public void f2 throws Exception{f1();
}
这里的f1出现异常,f2调用f1,那么f2为调用者,f1的异常会让f2来处理。
- 在类中写throws的时候得注意。
子类重写父类方法,对于抛出异常的规定为,子类重写方法抛出的异常要么和父类相同,要么为父类抛出的异常类型的子类。
那么如果抛出异常为编译异常给方法f1,那么f1就必须处理这个方法,可以选择将这个方法继续抛出,或者try-catch进行处理。
自定义异常
当程序出现了某种错误,但是该错误没有在JVM虚拟机中描述处理,这时候可以自己设计异常类,来处理异常。
定义类
自定义异常类名,继承Exception或者RuntimeException
如果继承Exception属于编译异常,如果继承RuntimeException属于运行异常,一般都继承于后者
应用实例
当我们接收Person对象年龄的时候,范围要在18-20,否则抛出一个自定义异常
package Exception_;/*** @another SunHaoyu* @verson 1.0*/
//当我们接收Person对象年龄的时候,范围要在18-20,否则抛出一个自定义异常
public class Exception_02 {public static void main(String[] args) {int age=22;if(age>=18&&age<=20){System.out.println("good");}else{throw new PP("must in 18-20");}}}class PP extends RuntimeException{public PP(String message) {super(message);}
}
这里看他在if判断之中,如果不属于就进行到了异常的判断,这里我们创建的pp类继承了运行异常,接着创建了其构造器,这里是使用了打印massage,所以我们可以将提示消息写在后面,以便输出提示。
throw和throws的区别
throw是手动生成异常对象的关键字,处于方法体当中,后面跟异常对象。
throws是异常处理的一种方式,处于方法声明处,后面跟异常对象。
常用类
包装类
针对八种基本数据类型的引用类型——包装类。
包装类和基本数据表类型的转换
装箱和拆箱。
装箱是指将基本类型转换为包装类型,反之则为拆箱
手动拆箱,装箱
手动装箱
int n1=100;
Integer integer =new Integer(n1);
Integer integer =Integer.valueOf(n1);
手动拆箱
int i =integer.intValue();
自动装箱,拆箱
jdk5以后就实现了自动装箱和自动拆箱
自动装箱
int n2=200;
Integer integer2=n2;
这里底层还是执行的valueOf方法
手动拆箱
int n3=integer2;
这里底层还是执行的intValue方法
小测试
下面输出什么
Object obj1=true? new integer(1):new Double(2.0);
System.out.println(obj1);
输出1.0 因为三目运算符中第一个是真的就输出第一个了,那么obj1就会等于integer(1)。但是这个三目运算符中最大的精度是double,所以输出的为1.0而非1.要将三目运算符看成一个整体
Intege类面试题
看看下面的代码都会输出什么结果
Integer i1=new Integer(127);
Integer i2=new Integer(127);
System.out.println(i1==i2);
注意这里创建了新的对象,是两个对象i1和i2,这两个对象是独立的,所以输出的结果是false。
Integer i3=127;
Integer i4=127;
System.out.println(i3==i4);
自动装箱,底层使用了valueOf的方法,要注意integer的范围是-128–127。是在这个范围里面的,所以是一个对象,输出True
Integer i5=128;
Integer i6=128;
System.out.println(i5==i6);
也是自动装箱,但是超出了valueOf的范围,创建了新的对象,所以是false。
可以从源码中看出创建了新对象
说白了就是,在valueOf范围里面的会直接从IntegerCache数组里取出一个值,如果不在里面,系统会自己创建一个值来使用。
String类
String对象是用于保存字符串,也就是一组字符序列
字符串常量对象是用双括号括起的字符序列
三个接口Serializable,Comparable,CharSequence
实现第一个接口Serializable,可以实现字符串进行串行化,代表着字符串可以在网络上传输
实现第二个接口Comparable,可以实现String对象之间的比较。
实现第三个接口CharSequence,SharSequence的值是可读可写序列,而String的值是只读序列。
创建String的两种方式
-
直接赋值的方式
String s="hsp";
先从常量池中查看是否含有hsp的数据空间,如果有,则直接指向,如果没有就会创建一个然后重新指向,s最终指向的是常量池当中的空间地址。
-
调用构造器进行创建
String s2=new String("hsp");
先在堆当中创建对象,里面维护了value的属性,指向hsp的常量池空间,如果有hsp,就会直接通过value指向堆中的空间地址,如果没有,就会重新创建。
intern方法
当调用这个方法的时候,如果池中已经包含了一个等于此String对象的字符串(使用equals(Object)方法确定),则返回池中的字符串,否则,将此String对象添加到池中,并返回此String对象的引用。
intern方法最终返回的是常量池当中的地址(对象)。
字符串的特性
String是一个final类,代表不可变的字符序列
字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的
String s1="hello";
s1="haha";
这里创建了几个对象?
创建了两个对象,其中第一步是在堆中创建了s1对象,第二步先在常量池中寻找,发现没有haha,就创建一个haha对象,然后将s1指向haha,但是hello对象并没有被释放。
String a="abc"+"bbb"
这里创建了几个对象?
创建了一个对象,编译器会自行将其优化成为String a=“abcbbb”
String a="abc";
String b="hello";
String c=a+b;
这里创建了几个对象?
首先前两行不用说,创建了两个对象,第三行中a+b
StringBuilder sb=StringBuilder();
sb.appended("hello");
sb.toString();//这里面创建一个新的对象,将两个相加取其前几位得到新字符串。
a和b指向池中创建的对象,然后c指向堆里面的value,value再指向池中的abchello,完成字符串的相加。
所以一共创建了三个对象。
String的常用方法
String类是保存字符常量的,每次更新都会重写开辟空间,效率较低,因此java设计者提供了StringBulider和StringBuffer来增强String的功能。
equals
区分大小写,判断内容是否相等
equalslgnoreCase
不分大小写,判断内容是否相等
length
获取字符串长度
indexOf
获取字符串第一次出现的索引,找不到返回-1
lastIndexOf
获取字符串中字符最后一次出现的索引,找不到返回-1
也可以搜索字符串,比如
String s1 ="weareone"
System.out.println(s1.indexof);
他会返回0,为所搜索字符串中第一个字符出现的位置。
trim
去前后空格
charAt
获取某索引处的字符,不能使用Str[index]这种方式。
substring
截取指定范围的字符串。(输入一个数字是,从索引处开始取他后面的字符。输入俩数字用逗号隔开是从第一个数字开始取,取第二个数字-1的长度。
toUpperCase
将字符串全部转换为大写的
tolowerCase
将字符串全部转换为小写的
concat
拼接字符串
String s1="hello ";
String s2="world";
System.out.println(s1.concat(s2));
最后会输出hello world,对字符串进行拼接
raplace
替换字符串中的字符
s1="hello hello hi"
s1=s1.replace("hello","hi");
最后的s1会变成hi hi hi,他会找到前者,然后将所有的hello替换成为hi。
注意:s1.raplace()这个方法返回的值才是替换过的,对于s1本身没有影响。
split
分割字符串
String ss="hello,world";
String[] w=ss.split(',');
他会根据,对ss进行一个分割,然后返回一个字符串数组,分割了几个部分,就是有几个元素的数组。
对于在进行分割的时候,如果有特殊字符,需要加入转义符\
String ss="hello\\world";
String[] w=ss.split("\\\\");
这样就可以正常输出
compareTo
比较两个字符串的大小,前面大返回正数,后面大返回负数。
他是相对应的字符(第一个不相同的字符)相减然后输出。假如长度不一样,会直接进行长度相减再输出。
toCharArray
转换成为字符数组
s="sorry";
char[] wa=s.toCharArray();
wa就会成为字符数组,里面的元素是sorry一个一个字符。
format
格式化字符串,和python相同。
StringBuffer类
StringBuffer代表可变的字符序列,可以对字符串内容进行增删。
StringBuffer是可变长度的
StringBuffer的构造器
- 直接创建
StringBuffer stringbuffer=new StringBuffer();
假如后面什么都不带,默认为16位的空间。 - StringBuffer(int i)
int来确定字符空间
StringBuffer stringbuffer=new StringBuffer(int i);
通过i来指定。 - StringBuffer(“String str”)
这样创建
StringBuffer stringbuffer=new StringBuffer(“hello world”);
空间是str.length()+16。
StringBuffer储存在哪里
他继承于父类的属性 char[] value 不是final,所以可以修改。
StringBuffer是一个容器
StringBuffer和String的对比
- String保存的是字符串常量,里面的值不能修改,每次String类得到更新实际就是更改地址,效率低
- StringBuffer保存的是字符串变量,里面的值可以进行修改。他放在堆中。
StringBuffer转为String
- 使用StringBuffer库提供的toString方法进行转化。
Sting s1=ss.toString();
- 通过构造器来指定
String s1 =new String(stringbuffer);
StringBuffer的常见方法
-
append
增StringBuffer s=new StringBuffer("hello"); s.append("world"); System.out.println(s);
会输出helloworld这个字符串。append这个方法会返回StringBuffer这个类型的值。
-
delete
删StringBuffer s=new StringBuffer("hello"); s.delete(0,2); System.out.println(s);
这里会删除从0-2的字符但是不包括2。
-
replace
修改StringBuffer s=new StringBuffer("hello"); s.replace(0,2,"ww");
这里会将0-2但不包括2的替换为ww
-
indexOf
查询
和String相同 -
insert
插入StringBuffer s=new StringBuffer("hello"); s.insert(1,"www");
这是原先在1的位置上插入ww,然后ww后移。
-
length
获取长度
StringBuilder类
- 一个可变的字符序列,此类提供与StringBuffer兼容的API,但不保证同步,他可以用于和StringBuffer进行一个简单替换,用于字符串缓冲区被单个线程使用的时候,可以优先使用StringBuilder。
- 他们继承于同一个父类,可以使用相同的方法
- 为final类不能被继承
Math类
Math类的常用方法
-
abs绝对值
-
pow求幂
int abs=pow(2,4);
代表2的四次方
-
ceil向上取整
-
floor向下取整
-
round四舍五入
-
sqrt求开方
-
random随机数
返回的是0-1的随机数
取2~7的数(int)(a)<=x<+(int)(a+Math.random()*(b-a+1))
-
max最大值
-
min最小值
Arrays类
arrays里面包含了一系列静态方法,用于管理或者操作数组。
方法
toString返回数组的字符串形式
Integer[] integers={1,2,3};
System.out.println(Arrays.toString(integers));
返回出 [1,2,3]
sort 排序(自然排序和定制排序)
Integer[] integers={1,-2,35,0};
Arrays.sort(integers);
//数组是引用类型,传入后会直接影响实参,即对原数组进行更改。
System.out.println(Arrays.toString(integers));
输出[-2,0,1,35],进行从小到大的排序
此外这个sort是进行重载的,可以通过传入一个接口Comparator实现定制排序
public static void main(String[] args) {Integer[] integers={-1,5,88,0,6};Arrays.sort(integers, new Comparator() {@Overridepublic int compare(Object o1, Object o2) {int i1=(Integer)o1;int i2=(Integer)o2;return i1-i2;}});System.out.println(Arrays.toString(integers));}
这里其中的i1和i2是可以进行调换的,i1-i2就是从小到大,i2-i1就是从大到小,因为这里是看是正数还是负数,然后返回到上一级进行排序,可以进入源码中看。
这里运用了二分排序,还有匿名内部类的操作。
binarySearch 通过二分搜索法进行查找,要求必须排好序,会返回索引
Integer[] integers={1,2,35};
System.out.println(Arrays.binarySearch(integers,1));
会输出0
不存在的数字,会直接返回-1。
如果是无序的数组,大概率会找不到,返回-1,或者返回错误的值
copyOf拷贝
进行拷贝的时候,如果新数组长度大于了原数组,就会在新数组多出来的位置上赋给null。
如果小于原数组,那么原数组多出来的元素则不会被拷贝上去。
equals比较两个数组元素是否完全一致
一样的返回true
不一样的返回FALSE
asList他会将()里面的数据转换成为一个list集合。
例题
自定义Book类,里面包含name和price,按照price排序从大到小,要求使用两种方式排序,book类里面有五个对象
package Exception_;import java.util.Arrays;
import java.util.Comparator;/*** @another SunHaoyu* @verson 1.0*/
//当我们接收Person对象年龄的时候,范围要在18-20,否则抛出一个自定义异常
public class Exception_02 {@Overridepublic String toString() {return "Exception_02{}";}public static void main(String[] args) {Book [] books=new Book[4];books[0]=new Book("a",1);books[1]=new Book("b",11);books[2]=new Book("c",12);books[3]=new Book("d",10);Arrays.sort(books, new Comparator<Book>() {@Overridepublic int compare(Book o1, Book o2) {int i1=o1.price;int i2=o2.price;return i2-i1;}});System.out.println(Arrays.toString(books));}}
class Book{String name;int price;public Book(String name, int price) {this.name = name;this.price = price;}@Overridepublic String toString() {return "Book" +"name='" + name + '\'' +", price=" + price;}
}
System类
常见的类
- exit 退出当前程序
- arraycopy 复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组
- currentTimeMillens返回当前时间距离1970-1-1(1970年1月1日)的毫秒数
- gc 运行垃圾回收机制 System.gc()
代码实现
System.out,println("ok11");
System.exit(0);
System.out,println("ok22");
我们会发现ok22不会输出,即为在第二步就退出了程序,0表示一个正常的状态。
int[] src={1,2,3};
int[] dest=new int[3];
System.arraycopy(src,0,dest,0,3);
第一个参数代表原数组,第二个参数表示从原数组哪个索引开始拷贝,第三个表示为目标数组,第四个参数是,将原数组的数据拷贝到新数组的哪个索引处,最后一个表示从原数组多少个数据拷贝到新数组。
BigInteger和BigDecimal类
BigInteger类
比如说要保存一个很大的整数超过了long的范围,不够用,这时候我们就可以使用BigInteger这个类来使用
BigInteger biginteger1=new BigInteger("264165165165165165");
BigInteger biginteger2=new BigInteger("4554658532526526522");
//如果后面数字还是太大,可以使用字符串将它包裹起来
//加减乘除的使用
//加
BigInteger biginteger3=biginteger1.add(biginteger2);
//biginteger就是返回出来相加后的值
//减法
biginteger1.subtract(biginteger2);
//除法
biginteger1.divide(biginteger2);
//乘法
biginteger1.multiply(biginteger2);
这些方法都会返回BigInteger类的数
BigDecimal类
这个是要对于一个精度很大的数字进行计算,即为小数点后面数字很多的时候,可以使用这个类来进行计算。
对bigdecimal进行计算也要使用相应的方法,需要创建其对象然后进行调用。
加减乘除方法与BigInteger相同。
但是在进行除法的时候有可能得到一个无限小数,会引起程序异常,这种情况下我们需要在divide方法后指定精度即可
bigdecimal.multiply(bigdecimal2,BigDecimal.ROUND_CEILING)
逗号后面的指,保留精度为分子和分母的程度。
日期类
第一代日期类
- date:精确到毫秒,代表特定的瞬间
- SimpleDateFormate
- parse
Date d1=new Date();//这里Data类是util下的,别引错包了
System.out.println(d1);//是外国风格的时间,可以使用下面的格式化时间
SimpleDateFormate sdf=newSimpleDateFormate("yyyy月MM月DD天")
String s="1996年01月01日";
Date parse=sdf.parse(s);//将字符串转换为时间,这里的s格式必须和sdf的格式相同,不然会报错
第二代日期类Calendar
可以通过这个类来获取时间
这里月份加一是因为,输出月份的时候是从0开始编号的
第三代日期类
第三代日期类的出现,是因为前两代存在着不足之处
- 可变性:像日期和时间这样的类应该是不可变的
- 偏移性:Date中的年份是从1990开始,而月份都从0开始
- 格式化:格式化只对Date有用,Calendar不行
- 此外,他们也不是线程安全的,不能处理闰秒等,每隔两天就会对出一秒。
常见的重要方法
-
now
返回当前日期时间的对象LocalDateTime ldt=LocalDateTime.now(); ldt.getYear(); ldt.getMonth();//输出英语名称 ldt.getMonthValue();//输出数字
-
DateTimeFormatter
格式化对象DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd")//按照上面表格中的进行书写,对他进行格式化 String foramt =dtf.format(ldt); //最后打印format即可
- Instant时间戳
Instat now=instant.now(); //通过from方法,可以将Instant转换为Date Date date=Date.from(now); //通过toInstant()可以将Date对象转换为Instant对象 Instant instant =date.toInstant();
-
可以获取到年月日 时分秒
LocalDate now =LocalDate.now();获得年月日 LocalDate now2=LocalTime.now();获得时分秒
- 可以对时间进行相加减提供了plus和minus方法
localDateTime localdatetime1=ldt.plusDays(892);
//返回892天后的时间
LocalDateTime localdatetime2=ldt,minusMinutes(3456);
//返回3456分钟前的时间
例题
例1
将字符串abcdef转换为aecdbf
package Exception_;
/*** @another SunHaoyu* @verson 1.0*/public class Exception_02 {public static void main(String[] args) {String str = "abcdef";try {str = reverse(str, 1, 41);} catch (Exception e) {System.out.println(e.getMessage());return;}System.out.println(str);}public static String reverse(String str, int start, int end) {if (!(str != null && start >= 0 && end > start && end < str.length())) {throw new RuntimeException("no no no");} else {char[] s = str.toCharArray();char temp = ' ';for (int i = start, j = end; i < j; i++, j--) {temp = s[i];s[i] = s[j];s[j] = temp;}return new String(s);}}
}
例2
输入用户名,密码,邮箱
要求用户名为3,4,5位数,密码为六位数而且都是数字,邮箱必须含有@和.并且@在.之前
package Exception_;
/*** @another SunHaoyu* @verson 1.0*/
public class Exception_02 {public static void main(String[] args) {String name;String pwd;String email;try {useruu("www","123456","5@.555");} catch (Exception e) {System.out.println(e.getMessage());}}public static void useruu(String name, String pwd, String email) {char[] usern = name.toCharArray();char[] userp = pwd.toCharArray();char[] usere = email.toCharArray();int i=email.indexOf('@');int j=email.indexOf('.');if (!(usern.length == 2 || usern.length == 3 || usern.length == 4)) {throw new RuntimeException("name is wrong");}if (!(userp.length == 6 && aa(pwd))){throw new RuntimeException("pwd is wrong");}if(!(i>0&&j>i)){throw new RuntimeException("emial is wrong");}else {System.out.println("good");}}public static boolean aa (String pwd){boolean flag = true;char[] user = pwd.toCharArray();for (int i = 0; i < user.length; i++) {if (!(Character.isDigit(user[i]))) {flag = false;}}return flag;}
}
集合
- 可以动态保存多个对象
- 提供一系列便于操作的方法:add remove set get等
- 使用集合来添加删除新元素的代码
Collection接口(单列集合)
单列集合:放的是单个的对象
特点
- collection实现子类可以存放多个元素,每个元素可以是object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类是有序的,有些不是有序的
- Collection接口没有直接实现子类的,是通过子接口list和set实现
List list = new Arraylist();
list.add("jack");
list.add(10);
……………………
方法实现
-
add添加
list.add("ddd");
-
remove指定删除某个元素(可以是索引,也可以是指定对象)
list.remove(10);//指定对象 list.remove(0);//索引
-
contains查找元素是否存在返回bollean值
list,contains("jack")//返回TRUE
-
size返回元素个数
list.size();
-
isEmpty判断是否为空,返回bollean值
list.isEmpty();
-
clear 清空元素
list.clear();
-
addAll添加多个元素
list2.add("red"); list2.add("blue"); list2.add("yellow"); list.addAll(list2);
-
containsAll查找多个元素是否都存在,返回bollean
list.containsAll(list2);
-
removeAll删除多个元素
list.removeAll(list2);
Collection接口遍历
-
使用Iterator迭代器,用于遍历Collection中的元素。
-
迭代器仅仅用于遍历集合,本身不用于存放对象。
-
所有实现了Collection的对象,都会获得一个迭代器。
Iterator的执行原理
对于迭代器来说,会有一个next方法用于下移取出元素,还会有一个hasNext方法,用于判断是否还有下一个元素,这样就构成的一个循环如下
while(ierator.hasNext){ System.out.println(ierator.next());
}
注意:在调用next方法之前必须得先判断是否含有下一个元素,否则当那个元素不存在的时候,编译器会抛出NoSuchElementException异常。
如果想要第二次遍历得重置迭代器,即再进行一遍赋值,使得next指向最前面
iterator = 对象.iterator();
快捷键生成while循环
itit
增强for循环
是简化版的迭代器
for(元素类型 元素名:集合名或数组名){ 访问元素;
}
代码实现
for(Object book:col){System.out.println(book);
}
col是一个书籍集合。
增强for的底层就是迭代器。
练习题
创建三个Dog{name,age}对象,放入ArrayList中,赋给List引用
使用迭代器和增强for循环两种方式来遍历
重写Dog的toString方法,输出name和age。
package CollectioN;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** @another SunHaoyu* @verson 1.0*/
// 创建三个Dog{name,age}对象,放入ArrayList中,赋给List引用
// 使用迭代器和增强for循环两种方式来遍历
// 重写Dog的toString方法,输出name和age。public class text {public static void main(String[] args) {List list =new ArrayList();list.add(new Dog("jack",1));list.add(new Dog("sam",2));list.add(new Dog("davi",3));Iterator iterator=list.iterator();while (iterator.hasNext()) {Object next = iterator.next();System.out.println(next);}System.out.println("====================");for(Object tt:list){System.out.println(tt);}}
}
class Dog{String name;int age;public Dog(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "name is "+name+" and age is "+age;}
}
Collection的子接口:List实现类
基本介绍
-
list集合类中的元素有序,即为添加和取出的顺序一致
-
list集合中的每个元素都有其相对应的顺序索引
-
list容器的每个元素都对应这一个整数型序号记载其在容器中的位置,支持索引取出
list.get(1);
-
list接口的实现类有:ArrayList,LinkedList和Vector等等
特有方法
List list =new ArrayList();
-
插入指定位置的元素
list.add(0,"one");
-
从一个位置出将else中的所有元素添加进来
List list2 =new ArrayList(); list2.add("two"); list2.add("three"); list.addAll(list2)
-
获取指定位置的元素
list.get(0);
返回one;
-
返回该元素在集合中最后一次出现的位置
list.LastindexOf("one");
-
返回该元素在集合中第一次出现的位置
list.indexOf("one");
-
移除指定位置的元素,并且返回该元素
list.remove(0);
-
设置指定位置元素为某某,相当于是替换
list.set(1,"xixixi");
-
返回从第一个到第二个位置的子集合
List list3=list.subList(0,2);
0~2左闭右开
练习题
使用list实现类添加三本图书,并且遍历,有名称,作者,价格三种属性,要求将价格从低到高排序。
package CollectioN;import java.util.ArrayList;
import java.util.List;/*** @another SunHaoyu* @verson 1.0*/
//使用list实现类添加三本图书,并且遍历,有名称,作者,价格三种属性,要求将价格从低到高排序。
public class text {public static void main(String[] args) {List list =new ArrayList();list.add(new Book(100,"ten","aaa"));list.add(new Book(20,"twenty","bbb"));list.add(new Book(304,"thirty","ccc"));for (int i=0;i<list.size()-1;i++){for (int j=0;j<list.size()-1-i;j++){Book b1 =(Book) list.get(j);//向下转型,取出对象。Book b2 =(Book) list.get(j+1);if(b1.price>b2.price){list.set(j,b2);list.set(j+1,b1);}}}System.out.println(list);}
}
class Book{int price;String name;String author;public Book(int price, String name, String author) {this.price = price;this.name = name;this.author = author;}@Overridepublic String toString() {return"price=" + price + '\t'+"name='" + name + '\t'+"author='" + author + '\t';}
}
ArrayList底层结构和源码分析
- 可以存放所有的值,并且可以存放空值,可以存放多个。
- ArrayList是由数组来实现数据储存的
- ArrayList基本等同于Vector,处理ArrayList是线程不安全的,在多线程情况下不建议使用ArrayList。
底层操作机制源码分析
- ArrayList中维护了一个Object类型的数组elementDate
- 当创建了ArrayList对象的时候,如果使用的是无参构造器,则初始elementDate容量为0,第一次添加,则扩容为10,再次扩容为上次的1.5倍。自己指定大小需要扩容也是1.5倍。
Vector底层结构和源码分析
- vector的底层是一个对象数组
- vector是线程同步的,即为线程安全的,vector类的操作方法中带有synchronized关键字,即为线程安全。
- 无参情况下,默认为10位,满了之后按2倍扩容,指定大小满了也是2倍扩容。
LinkedList底层结构
- 实现了双向链表和双端队列的特点。
- 可以添加任意元素包括null。
- 线程不安全,不能实现同步。
First和Last分别指向他的首结点和尾结点。
CRUD(增删改查)
package LInkedList;/*** @another SunHaoyu* @verson 1.0*/
public class NNNOOODDDEEE {@SuppressWarnings({"all"})public static void main(String[] args) {Node jake = new Node("jake");Node tom = new Node("tom");Node lisa = new Node("lisa");jake.next=tom;tom.next=lisa;lisa.prev=tom;tom.prev=jake;Node first=jake;Node last=lisa;while(true){if(first==null){break;}else{System.out.println(first);first=first.next;}}}
}
class Node{public Object item;Node prev;Node next;public Node(Object item) {this.item = item;}@Overridepublic String toString() {return "item=" + item;}
}
这里使用了while循环来遍历数组。
当我们需要插入一个结点时,通过下面的代码实现。比如说我现在将yy插入到tom和lisa中。
Node yy = new Node("YY");tom.next=yy;yy.next=lisa;lisa.prev=yy;yy.prev=tom;
程序再次进行遍历后为
说明插入成功。
只需要这四步即可
下面是CRUD的解决方法
比如添加的方法是add,删除为remove,改为set,查为getFirst方法,下面我以add举例说明
package LInkedList;import java.util.LinkedList;/*** @another SunHaoyu* @verson 1.0*/
public class NNNOOODDDEEE {@SuppressWarnings({"all"})public static void main(String[] args) {LinkedList linkedList=new LinkedList();linkedList.add(10);linkedList.add(2);System.out.println(linkedList);}
}
- 程序在第一步,先进行了一个空结点的创建,此时的first和last都为null。
- 接着进入一个方法,创建一个新结点
- 接着将这个新节点加入到双向结点最后,此时的first和last都指向这个新结点。
- 程序进行到add(2)的时候,进入方法之中,他将last指向新节点,并且将新节点的pre指向旧结点的pre,然后将旧结点的next指向新结点的next,程序进行size++扩容数字之后,程序进行完毕。
linkedList.remove();
里面如果不写东西,默认删除第一个结点。
Collection的子接口:Set实现类
- 是无序的(添加和取出的顺序不一样,但是第一次取出来是什么结果,以后都会是这个结果),没有索引
- 不允许重复元素,所以最多包含一个null
- 遍历它通常使用迭代器或者增强for循环
- set接口常用方法与Collection相同
package SET;import java.util.LinkedHashSet;
import java.util.Set;/*** @another SunHaoyu* @verson 1.0*/
public class SSEETT
{@SuppressWarnings("all")public static void main(String[] args) {Set set = new LinkedHashSet();set.add("amy");set.add("amy");set.add(10);set.add(1);set.add(new Dog("ww"));set.add(new Dog("ww"));set.add(new String("qq"));set.add(new String("qq"));System.out.println(set);}
}
class Dog{String name;public Dog(String name) {this.name = name;}@Overridepublic String toString() {return name ;
可以发现这里的amy因为重复没有再进行添加,Dog类是创建了两个不同的对象不会进行添加,那么String类是因为,调用了equals进行比较,发现相同就不会进行二次添加,每个类的equals方法都不一样,String类是看是不是相同的,其他的不一样。程序员也可以通过重写equals方法来达到自己的目的。
HashSet全面说明
HashSet的底层是HashMap,HashMap的底层是(数组加链表加红黑树)
是这样的一个形式,数组中的可以为链表的储存方式,这样的储存方式很高效,当数据大于64的时候会进行树化。
我先用一段代码进行大致的理解
package SET;/*** @another SunHaoyu* @verson 1.0*/
public class HashSET {public static void main(String[] args) {Node table[]=new Node[16];Node jake = new Node("jake", null);table[2]=jake;Node john=new Node("john",null);jake.next=john;Node sam = new Node("sam", null);john.next=sam;}
}
class Node{public Object item;Node next;public Node(Object item, Node next) {this.item = item;this.next = next;}
}
可以发现为链装进行保存。
解释
- 添加一个元素的时候会得到他的hash值,由hash值进行转换,变为索引值。
- 找到存储数据表table,看这个索引位置是否有已经存放了的元素,如果没有就会直接加入
- 如果有元素,会通过equals比较,如果相同就会放弃添加,不相同会添加到最后
- 在java8当中,如果一条链表的元素个数到达8,并且table大小大于等于64,就会进行树化,即为红黑树。
HashSet源码解读
public V put(K key, V value) {//调用putVal方法完成putreturn putVal(hash(key), key, value, false, true);}/*** Implements Map.put and related methods** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//判断table是否初始化,没有则执行初始化操作。if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//计算键值对的存储位置,如果为空,则直接赋值if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//判断该key是否存在,存在则直接赋值if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//判断链表是否为红黑树else if (p instanceof TreeNode)//执行红黑树操作e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//链表for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {//添加链表p.next = newNode(hash, key, value, null);//如果链表的长度为8,将链表转化为红黑树存储if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}//判断该key是否存在,存在则直接赋值if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//e代表了存在相同key的Node节点对象。如果e不等于空,则把旧的值覆盖为新的值。返回旧值,不执行size++if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)//重新赋值e.value = value;afterNodeAccess(e);return oldValue;}}//集合修改次数++modCount;//判断是否需要扩容(当前size(键值对数量)+1>阈值)if (++size > threshold)//扩容方法resize();afterNodeInsertion(evict);//空操作return null;}
这是HashMap的源码
这里面存在这负载因子为0.75,当内存已经使用了0.75的时候,就会对内存进行扩容处理,作为缓冲。第一次直接是16位内存,16*0.75=12,所以当用到12的时候,就会就行扩容。扩容两倍。
这个加入的话,只要是添加元素就算一位size就是++,无论是加入到数组当中还是加入到数组某一位的链表当中,都会size++,加到12之后就会进行扩容。
我们知道,存入这里面的索引是计算hash码得出的,有可能两种不同的明文也会计算出一种hash码,接着进入equals方法进行比较,如果两个对象相同就不会进行添加,不同的话会进行添加处理。
练习题
创建Employee类,创建三个对象存入hashset中,如果年龄和名字相同就不能存入
package SET;import java.util.HashSet;
import java.util.Objects;/*** @another SunHaoyu* @verson 1.0*/
@SuppressWarnings("all")
public class HashSET {public static void main(String[] args) {HashSet hashSet = new HashSet();hashSet.add(new Employee("mike",12));hashSet.add(new Employee("mike",15));hashSet.add(new Employee("mike",12));System.out.println(hashSet);}
}
class Employee{private String name;private int age;public Employee(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Employee employee = (Employee) o;return age == employee.age && Objects.equals(name, employee.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
这段代码的最后61–65,意思是如果这个对象的name和age相同,就返回相同的hashcode值,这样程序才会进入下一步,进行equals比较,最终返回ture,不将重复对象进行添加。
练习2
定义一个Employee,包含name,sal还有birthday(MyDate类),birthday为MyDate类,包含year,day,month,创建三个Employee放入HashSet,当name和birthday相同的时候,认为是相同员工,不添加到HashSet集合之中。
package SET;import java.util.HashSet;
import java.util.Objects;/*** @another SunHaoyu* @verson 1.0*/
@SuppressWarnings("all")
public class HashSET {public static void main(String[] args) {HashSet hashSet = new HashSet();hashSet.add(new Employee("mike",100,new MyDate(1,1,1)));hashSet.add(new Employee("lisa",120,new MyDate(1,5,7)));hashSet.add(new Employee("mike",100,new MyDate(1,1,1)));System.out.println(hashSet);}
}
class Employee{private String name;private int sal;private MyDate birthday;public Employee(String name, int sal, MyDate birthday) {this.name = name;this.sal = sal;this.birthday = birthday;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getSal() {return sal;}public void setSal(int sal) {this.sal = sal;}public MyDate getBirthday() {return birthday;}public void setBirthday(MyDate birthday) {this.birthday = birthday;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Employee employee = (Employee) o;return Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);}@Overridepublic int hashCode() {return Objects.hash(name, birthday);}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", sal=" + sal +", birthday=" + birthday +'}';}
}
class MyDate{int year;int month;int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;MyDate myDate = (MyDate) o;return year == myDate.year && month == myDate.month && day == myDate.day;}@Overridepublic int hashCode() {return Objects.hash(year, month, day);}@Overridepublic String toString() {return "MyDate{" +"year=" + year +", month=" + month +", day=" + day +'}';}
}
LinkedHashSet解读
- LinkedHashSet是HashSet的子类
- LinkedHashSet的底层是一个LinkedHashMap,底层维护了一个数组加上双向链表
- LinkedHashSet根据元素的hashcode值来决定元素的储存位置,同时使用链表来维护元素的次序,这使得元素是看起来以插入顺序进行保存的
- LinkedHashSet不允许添加重复元素。
添加原理
他是添加的过程中,首先算出该对象的hashcode值,然后确定好索引插入,第二个值添加的过程中和第一个值一样,确定好索引添加,但是他会和第一个值建立联系,即第一个值before的指向第二个值,第二个值的after指向第一个值,形成双向链表。这样是看起来以插入顺序进行保存的,不能添加相同元素,这一点和HashSet是相同的。
Map接口(双列集合)
双列集合:存放的是键值对
特点
-
Map与Collection并列存在,用于保存具有映射关系的数据key-value
-
Map中的key和value可以是任意引用类型的数据,会封装到HashMap$Node对象中
-
Map中的key不允许重复,原因和HashSet相同
有相同的key时,value会进行替换。 -
Map中的value可以重复
-
Map中的key可以为null
-
常用String类作为Map的key,Object类都行。
-
key和value之间存在单向一对一的关系,即为通过指定的key总能找到对应的value。
通过get方法,传入一个k,会返回一个value -
Map存放数据的key—value,一对key是放在一个Node中的,因为Node实现了Entry接口,有些书上说一对k—y就是一个Entry
真正的key—value是放在HashMap$Node里面的,但是Set—Collection指向了他
key-value存放在HashMap$Node里面,key-value为了程序员遍历,就创建了一个EntrySet集合,该集合存放的元素类型为Entry,而一个Entry对象就有k,v
entry中,定义的类型是MapEntry,但是存放的类型是HashMap$Node,因为Node实现了mapentry类
MapEntry提供了两个很重要的方法,一个是getKey,一个是getValue方法
key存放的在Set,value存放在Collection里面
方法
- remove 根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为0
- clear:清除
- containsKey:查找键是否存在
- put:添加值
遍历Map
-
containsKey:查找键是否存在
-
keySet:获取所有的键
public class text {@SuppressWarnings("all")public static void main(String[] args) {Map map= new HashMap();map.put("ww","www");map.put("qq","qqq");Set keyset=map.keySet();for(Object key: keyset){System.out.println(key);}} }
-
entrySet:获取所有关系
Set entryset =map.entrySet();for(Object d: entryset){Map.Entry m=(Map.Entry) d;System.out.println(m.getKey()+"---"+m.getValue());}
-
values:获取所有的值
Collection value=map.values();Iterator iterator = value.iterator();while (iterator.hasNext()) {Object next = iterator.next();System.out.println(next);}
练习
使用HashMap添加三个员工对象,要求键是员工id,值是员工对象
遍历工资大于18000的员工
员工类:姓名 工资 id
package MAP;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** @another SunHaoyu* @verson 1.0*/
public class text {
@SuppressWarnings("all")public static void main(String[] args) {Map map =new HashMap();map.put(1, new Person(1,"mike",18000));map.put(2,new Person(2,"shy",20000));map.put(3,new Person(3,"yy",555555));Set keys =map.keySet();for (Object k:keys){Person o = (Person) map.get(k);
//返回类型需要转型,不然无法创建Person对象if(o.gongzi>18000) {System.out.println(o);}}Set entryset=map.entrySet();for(Object o: entryset){Map.Entry a=(Map.Entry) o;Person w= (Person) ((Map.Entry) o).getValue();//先进行Map.Entry的转型,使用getValue方法,接着在进行Person转型,创建对象进行判断。if(w.gongzi>18000) {System.out.println(o);}}}}class Person{int id;String name;int gongzi;public Person(int id, String name, int gongzi) {this.id = id;this.name = name;this.gongzi = gongzi;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", gongzi=" + gongzi +'}';}
}
HashMap底层源码分析
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象的时候,将加载因子初始化为0.75
- 当添加Key—Value时,通过key的哈希值得到在table上的索引,然后确定该索引处有没有元素,没有元素则会直接添加。如果有元素,会进行key值的比较,如果key值相等,会进行value的替换,如果key值不相等,会进行链表添加或者树化,当内存不够的时候会进行扩容。
- 第一次添加的时候,table的容量为16
- 以后再进行扩容的时候,会进行两倍的添加
- 在Java8中,当一条链表元素超过8,并且table大小大于等于64的时候,就会进行树化。
HashTable基本介绍
- 存放的元素是键值对
- hashtable的键和值都不能为null
- hashtable使用方法基本和hashmap一样
- hashtable是线程安全的,hashMap是线程不安全的
HashTable底层
- hashtable初始化底层数组大小为11
- 临界值为0.75
- 扩容
新的容量是原先的容量乘2再加上1。
Properties
- Properties类继承于HashTable并且实现了Map接口,也是一种使用键值对形式来进行数据保存的
- 他的使用特点与HashTable类似
- Properties可以用于从xxx.Properties文件中,加载数据到Properties类对象,并且进行读取和修改。
如何选择集合
TreeSet
可以使用TreeSet提供的构造器,加入比较器写内部类来实现。
public class TreeText {public static void main(String[] args) {TreeSet treeSet=new TreeSet(new Comparator() {@Overridepublic int compare(Object o1, Object o2) {return ((String)o1).compareTo((String) o2);}});}
}
TreeMap
方法和TreeSet相同
Collections工具类
- Collections是一个操作Set,List和Map等集合的工具类
- Collections中提供了一系列静态方法对集合元素进行排序,查询,修改等操作
排序操作
- reverse(list):反转list中的元素排序
- shuffle(list):对list集合中的集合元素进行随机排序
- sort(list):根据元素的自然顺序对指定list集合元素按升序排序
- sort(list ,COmparator)根据指定的Comparator产生的顺序对List集合进行排序
- swap(list ,int,int)将指定的集合i处和j处的元素进行交换。
查找,替换
- Object max(Collection):根据元素的自然排序,返回给定集合中最大的元素
- Object max(Collection ,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection ,Comparator)
- int frequency(Collection,Object)返回指定集合中指定元素出现的次数
- void copy(list dest,list scr)将scr中的内容复制到dest中
- boolean replaceAll(List list ,Object oldVal,Object newVal):使用新值替换list对象的所有旧值。
泛型
对于传统问题来说,不能对加入到集合中的数据进行约束
遍历的时候,需要进行类型转换,如果集合中的数据量大,对效率会有影响。
快速入门使用
ArrayList<Dog> arraylist=new ArayList<Dog>();
这里泛型的使用规定了只能将Dog类放入ArrayList集合中,如果加其他类型,就会报错。
写上规定之后,当取出的时候就可以直接取出,不用进行类型转换。
介绍
-
泛型又称为参数化类型,是JDK5.0出现的新特性,解决数据类型的安全性问题
-
在类声明或实例化时只要指定好需要的具体类型即可
-
Java泛型可以保证程序在编译的时候没有警告,运行就不会产生ClassCastException异常,同时使得代码更加简洁健壮
-
泛型的作用是可以在类声明时通过一个标识表示某个属性的类型,或者是某个方法的返回值类型,或者是参数类型。
package generic;/*** @another SunHaoyu* @verson 1.0*/ public class text {public static void main(String[] args) {Dog<String> hhh = new Dog<>("hhh");Dog<String> dog = new Dog<String>("ww");} } class Dog<E>{E age;public Dog(E age) {this.age = age;} }
这个E是数据库类型,在创建新对象的时候指定,第一次是什么类型,以后只能是那个类型,不能改变,对于上面的代码而言,相当于将String把E进行了替换。
泛型的声明
interface接口{}和class类<K,V>{}
- 其中T,K,V不代表值,而是表示类型
- 任意字母都可以,常用T表示,是Type的缩写
泛型的实例化
要在类名后面值的类型参数的值(类型),和上面的代码一样。
练习
创建三个学生对象
放入到HashSet中使用学生对象
放入到HashMap中,要求key是String name,value是学生对象
使用两种方式遍历
package generic;import java.util.*;/*** @another SunHaoyu* @verson 1.0*/
@SuppressWarnings("all")
public class text {public static void main(String[] args) {Set<Strudent> set=new HashSet<Strudent>();set.add(new Strudent("davi",5));set.add(new Strudent("halle",8));set.add(new Strudent("mike",15));Map<String, Strudent> map=new HashMap<>();map.put("davi",new Strudent("davi",5));for(Object w:set){System.out.println(w);}Iterator<Strudent> iterator = set.iterator();while (iterator.hasNext()) {Strudent next = iterator.next();System.out.println(next);}}
}class Strudent{String name;int age;public Strudent(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Strudent{" +"name='" + name + '\'' +", age=" + age +'}';}
}
注意事项和使用细节
- inferface List{},public class HashSet{}等等
说明T,E只能是引用类型 - 在指定泛型之后,可以传入该类型的子类或者该类型
- 如果我们这样写List list3=new ArrayList();,默认给他的泛型就是【就是Object】