01.JavaSE学习
一、java入门
java三大版本(write once,run anywhere)
- JavaSE:标准版(用于桌面开发,控制台开发)
- javaME:嵌入式开发(手机,小家电)
- javaEE:以javaSE为基础,E企业级开发(web端,服务器端开发)
JDK、JRE、JVM
- JDK:Java Development Kit(java开发者工具)
- JRE:Java Runtime Environment(java运行时环境)
- JVM:Java Virtul Machine(java虚拟机)
安装jdk之后,jdk安装目录中的各目录的作用
bin目录:存放一些可执行的程序
include目录:存放需要引入的c和c++的头文件
jre目录:java运行环境需要的一些文件
lib目录:存放java 的类库文件,比如jvm等
src.zip:存放java 的一些资源文件,一些常用类库,解压之后就是sun公司编写的一些类的源代码,比如io类,string类等。
helloworld程序
// 1.新建一个java文件,java文件后缀名为.java,新建hello.java文件
public class Hello{
public static void main(String []args){
System.out.print("hello world!");
}
}
//打开dos窗口,在dos窗口中编译java文件
//编译java文件:javac Hello.java,编译之后产生一个hello.class文件 (javac就是一个编译的命令,放在bin目录下面)
//运行class文件:java Hello (java就是一个运行程序的命令,放在bin目录下面)
java程序的两种运行机制
编译型:将所有代码全部编译,然后再执行
解释型:代码编译一句执行一句。
java文件是先编译为class文件,然后通过解释器一句一句将class文件解释给操作系统从而执行。(先编译后解释)
二、基础语法
注释:单行注释、多行注释、文档注释
//单行注释:注释一行 /*多
行
注
注释:注释多行代码
*/ //文档注释:/** + 回车
/**
*
* @param args
*/
关键字和标识符
- java关键字:java中具有特定功能的标识符
- 标识符:不能跟关键字一样。类名、方法名、变量名都是标识符。标识符由字母、数字、下划线(-)、美元符($)组成,不能由数字开头,大小写敏感。
数据类型
两种类型的编程语言:强数据类型和弱数据类型,java是强数据类型。
强数据类型:变量的使用要严格符合规定,所有变量都必须先定义再使用。
弱数据类型:变量的使用规定不严格。
java的数据类型分为两大类:基本类型和引用类型
注意!!!:java中整数的默认数据类型是int型,也就是说,只要有一个整数类型的数据,未被明确定义或者转换为某一个具体的数据类型,那么它就会被当成一个int型数据处理。(比如直接输出一个byte数据+char数据,结果会是一个int数据。),浮点型数据则默认是double类型。
//整数
byte b = 10;
short s = 10;
int i = 10;
long l = 10L; // Long类型需要在其后面加上 L 或者 l 标识 //浮点数
float f = 10.00F; //float类型需要在后面加上 f 或者 F
double d = 10.00; //字符类型
char c = 'd'; //布尔值
boolean bo = true; //只有两个值,true或者 false;
一些关于数据类型的扩展
//整数扩展
//进制: 二进制 0b、八进制 0、十进制、十六进制0X
int i1 = 0b0101; //二进制数字:表示二进制5;
int i2 = 0100; //八进制数字:表示八进制64;
int i3 = 100; //十进制数字:表示十进制100;
int i4 = 0X0100; //十六进制数字:表示十六进制256; //浮点数扩展(比如面试题,银行业务怎么表示?也就是怎么算钱?使用数学工具类:BigDecimal
//float:表示范围是有限的,表示的数据是离散的,会有舍入误差,它表示的数据可能接近但不等于真实数据。
//double
//最好完全避免使用浮点数去进行比较
//最好完全避免使用浮点数去进行比较
//最好完全避免使用浮点数去进行比较
float a = 0.1f; //0.1
double b = 1/10; //0.1
System.out.println(a == b); //false,浮点数是有舍入误差,需要完全避免使用浮点数去进行比较 float aa = 23123131312132f;
float bb = aa+1;
//true,因为aa表示的数据已经超过最大值了,所以bb+1,也无法超过最大值,所以相等。
System.out.println(aa == bb); //字符扩展
//所有的字符本质上都是整数,所以可以用它来进行整数之间的计算,也可以很容易地转化为整数。
//转义字符: \t,\n等等很多。
数据类型转换:强制类型转换和自动类型转换
自动类型转换:不同类型的数据进行运算的时候,会先自动转换为统一数据类型,在进行运算。自动转换的规则:byte,short,char -> int -> long -> float -> double
强制类型转换:将数据类型强制转换为另一种类型的数据。格式:(目的数据类型) 原数据
int a = 128;
byte b = (byte)a;
System.out.println(b);
//输出-128,因为byte的数据类型占一个字节,最大值为127,将128的int数据赋予byte变量,会发生数据溢出。
数据类型转换的注意点:a) 不能对布尔类型数据进行转换;b) 不能把对象类型转化为不相干的类型; c) 把高容量数据转换为低容量的数据时,需要进行强制类型转换; d) 转换的时候可能发生内存溢出或者精度缺失问题。
变量、常量和作用域
变量:可以变化的量,其实就是声明指定了一部分的内存空间。java是一种强数据类型,所以每个变量都需要声明其数据类型。声明的一般格式:数据类型 标识符;或者 数据类型 标识符 = 值;也可以使用逗号,一次声明多个相同类型的变量
int a;
int b=1;
int c,d,e;
int f =1,g=2,h=3;
作用域:局部变量、实例变量、类变量
局部变量:定义在方法中,只能在方法中使用;必须先声明并赋值才能使用。
实例变量:从属于一个实例对象的变量,只能被本类的实例调用;需要先声明,但是无需赋予初值,未赋值时使用默认值。(默认值:布尔类型(false)整数或者浮点数(0 或 0.0)引用数据类型(null)
类变量:使用static关键字修饰的成员变量,从属于一个类的变量,与一个类同在。
public class Hello {
int a;
String b = "实例变量"; //这俩货是实例变量
static String c = "yyds"; //这货是个类变量 public static void main(String []args){
System.out.println((new Hello().b)); //使用类对象调用实例变量
System.out.println(c);
//在类内部调用本类的类变量,直接引用即可。
// 如果是在其他类调用本类的变量,需要使用 类名.变量名 的格式调用
} public void other(){
int a;
int b = 1; //这俩货是局部变量
System.out.println(b); //调用局部变量,需要先赋值
}
}常量:初始化之后,值就不会再改变的变量。定义格式:public static final 数据类型 变量名 = 值;(常量变量名一般使用大写字母和下划线组成)
常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。常量的执行原理:在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量。(这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。)
基本运算符
算术运算符:+, - ,* ,/ ,% (注意运算过程中的数据类型的转换原则)
//+用于表示加法运算之外,也可用于进行字符串拼接。
int a = 1;
int b = 2;
System.out.println(""+a+b); //输出12:将空白的字符串和a和b的值拼接起来,所以是12;
System.out.println(a+b+""); //输出3:由于空字符串在a+b之后,所以会先将a+b进行加法运算,再和空字符串拼接。
自加自减运算符:++,--(注意++,--放在变量的前面或者后面的位置变化的区别)
int a=1;
int b=a++;
int c=++a;
System.out.println(a); //a进行了两次自加,所以是3
System.out.println(b); //++在a的后面,所以将a的值1赋予b,然后再将a+1 = 2;
System.out.println(c); //++在a的前面,所以将a+1,然后再将a的值赋予b,此时a的值是2+1=3
赋值运算符:=
关系运算符:>,<,>=,<= ,==,!=,对象 instanceof 类(用于判断对象是否属于类)
逻辑运算符:&&(注意短路运算,当前面的判断为false时,后面的代码将不再执行),||,!
//注意&&的短路运算
int a = 1;
int b = 1;
System.out.println((a==2) && (++b)==2);
System.out.println(b);
//输出的b的值是1.因为在上面的&&逻辑运算中,a==2的结果为false,那么将不再执行后面的代码将b+1,所以b还是1不变。
位运算符:<<,>>,&,|,~,>>>(位运算的运算效率很高)
条件运算符:判断条件?运算1:运算2;(进行条件判断,结果为true则进行运算1,为false则进行运算2)
扩展赋值运算符:+=,-=,*=,/=
包机制:项目中可以创建一些包,相当于不同层级的目录。将类放在不同的包中可以区分不同的类。(一个包中不能存放类名相同的类,不同的包中可以存在类名相同的类。)
java文件中需要使用 “package 包路径” 来说明当前类所处的位置。
在一个java文件中引用类的时候必须要使用import导入,格式为:import 包1.包2.类。假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只角
能导入一个类,另一个类要带包名访问。例如:package first.second; //java文件中需要使用 “package 包路径” 来说明当前类所处的位置。
import first.Shh; //由于当前java文件中使用了不同包的类,所以需要import 关键字引入该类。否则将报错。 public class Hello {
public static void main(String []args){
Shh shh = new Shh(); //使用了一个别的包中的类
}
}
JavaDoc:javaDoc可以用来生成自己的API文档。通过一些参数对类或者方法进行说明,主要参数有:"@author","@version","@since","@param","@return","@throws 异常等"。
package first.second;
/**
* @author shh (作者)
* @version 1.0 (版本)
* @since 1.1 (日期)
*/
public class Hello {
/**
* @param a 参数说明
* @param b
* @return 返回值说明
* @throws Exception 表示抛出了异常
*/
public int method(int a,int b) throws Exception{
return a;
}
}
同时javadoc也是一个java的可执行程序,在dos窗口中执行:javadoc -encoding UTF-8 -charset UTF-8 hello.java即可生成hello.java类的说明文档 (注意:需要先进入要生成文档的java文件所在的目录,也可使用IDEA直接生成javadoc)
(-encoding UTF-8 -charset UTF-8参数是为了设置编码集,防止中文乱码)
三、流程控制
1.Scanner类
Scanner的作用:用于实现程序和人的交互,可获取用户输入的信息。
Scanner类的常用方法
String next():获取输入数据的第一个字符串,比如hello word,则会获取到hello。next0):
1、一定要读取到有效字符后才可以结束输入;
2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉;
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符;
4、next() 不能得到带有空格的字符串;String nextLine():获取数据输入的一行数据
1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符
2、可以获得空自,只要回车了,就会将那一行的数据都获取出来。boolean hasNext():是否还有下一个字符串
boolean hasNextLine():判断是否还有下一行数据
基本的使用方法
//使用hasNext以及next方法:若输入 12 12 12,将打印三次12
public class Hello {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){ //判断是否还有下一个输入
String str = scanner.next(); //获取输入的数据
System.out.println(str);
}
scanner.close(); //凡是涉及到IO流的都要注意使用完毕之后关闭该流
}
} //使用hasNextLine以及nextLine:若输入 12 12 12,将打印一次 12 12 12
public class Hello {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){ //判断是否还有下一行数据输入
String str = scanner.nextLine(); //获取输入的一行数据
System.out.println(str);
}
scanner.close(); //凡是涉及到IO流的都要注意使用完毕之后关闭该流
}
}
Scanner的进阶使用:Scanner类中有大量的针对于某一个数据类型的 nextXXXX() 方法和 hasnextXXX() 方法,比如:int nextInt()、byte nextbyte()、float nextFloat()等等。比如hasNextInt()、hasNextbyte()、hasNextFloat()等等
public static void main(String[] args) {
//输入多个数据,并求其总和与平均数,每输入一个数字用回车确定,通过输入非数字来结束输入并输出执行结果。
Scanner scanner = new Scanner(System.in);
int sum = 0;
int count = 0;
while(scanner.hasNextInt()){
count++;
sum = sum + scanner.nextInt();
}
System.out.println("输入的所有数据的总数为:"+sum);
System.out.println("平均数为:"+sum/count);
scanner.close();
}
2.流程控制结构
顺序结构:java最基本的执行结构就是顺序机构,也就是在没有其他流程控制的情况下, 代码都是按顺序依次执行的。
if...else 选择结构:基本机构:if(判断条件){如果判断结果为true,则执行运算1}else if (判断条件){运算2}else{运算3}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = 0;
if (scanner.hasNextInt()){
a = scanner.nextInt();
if(a<60){
System.out.println("不及格");
}else if(a>=60 && a<80){
System.out.println("良好");
}else if(a>=80 && a<90){
System.out.println("优秀");
}else{
System.out.println("变态");
}
}
else{ System.out.println("请输入数字"); }
scanner.close();
}
switch case多选择结构。
switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。switch 语句中的变量类型可以是:byte、short、int 或者 char。
从 Java SE 7 开始,switch 支持字符串String 类型;同时 case 标签必须为字符串常量或字面量。注意:如果选中了某一个case,然后这个case中没有使用break语句,那么将依次执行那个case以及它之后的所有case;若没有符合的case,则会执行default;
/* 基本结构:
switch(expression){
case value
语句;
break;
可选case value :
语句;
break;
你可以有任意数量的case语句
可选default :
语句
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = 0;
if (scanner.hasNextInt()){
a = scanner.nextInt();
switch (a){
case 1:
System.out.println("挺好");
break;
case 2:{
System.out.println("真挺好");
break;
}
default: //若没有符合的case,则会执行default;
System.out.println("是个天才啊");
break;
}
}
else{ System.out.println("请输入数字"); }
scanner.close();
}
while循环结构
a.只要布尔表达式为 true,循环就会一直执行下去
b.我们大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环.
c.少部分情况需要循环一直执行,比如服务器的请求响应监听等。
d.循环条件一直为true就会造成无限循环[死循环],我们正常的业务编程中应该尽量避免死循环。会影响程序性能或者造成程序卡死奔溃!/*while循环基本结构
while(条件判断){
循环内容;(当条件判断为true时,就会循环执行这部分内容)
}
*/
public static void main(String[] args) {
//实现 1+2+。。。+100
int i=1;
int result = 0;
while(i<=100){ //循环100次
result+=i++;
}
System.out.println(result);
}
do...while循环结构
对于while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件也至少执行一次。
while 循环相似,不同的是do...while 循环是先循环后判断,while循环是先判断后循环;do...while 循环至少会执行一次
do,while循环可能一次循环体也不执行。/*do...while循环基本结构
do{
循环内容;
}while(条件判断);
*/
public static void main(String[] args) {
//实现 1+2+。。。+100
int i=1;
int result = 0;
do{
result +=i;
}while(i++<100);
System.out.println(result);
}
for循环结构
for循环的基本使用
for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构。for循环执行的次数是在执行前就确定的。语法格式如下:
for(初始化;布尔表达式;更新) {循环代码语句}
练习1:计算0到100之间的奇数和偶数的和
练习2:用while或for循环输出1-1000之间能被5整除的数,并且每行输出3个
练习3:打印九九乘法表public static void main(String[] args) {
//实现 1+2+。。。+100
int result=0;
for (int i = 1; i <=100 ; i++) {
result += i;
}
System.out.println(result);
}
关于for循环的注意事项:最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句。
然后,检测布尔表达式的值。如果为 true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
执行一次循环后,更新循环控制变量(送代因子控制循环变量的增减)。
再次检测布尔表达式。循环执行上面的过程。
(for循环中,for右边括号中的每条语句都可以舍弃,三个部分的任意排列组合都符合语法可以正常运行。但是就算舍弃了哪一部分条件,分号也不能丢。)
增强for循环:用于遍历数组或者集合
Java增强 for 循环语法格式如下:for(声明语句 : 表达式){代码句子;}
声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配,其作用域限定在循环语句块,其值与此时数组元素的值相等。表达式:表达式是要访问的数组名,或者是返回值为数组的方法
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for(int x:arr){ //奇怪的写法
System.out.println(x);
}
}
for循环联系:打印三角形:
/*打印n行的三角形
*
***
*****
*******
*********
*/
public void print(int n){
for (int i = 1; i <= n; i++) {
for (int j = 1; j <=n-i ; j++) {
System.out.print(" ");
}
for (int j = 1; j <= 2*i-1; j++) {
System.out.print("*");
}
System.out.println();
}
}
3.流程控制关键字
break关键字:break在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环不执行循环中剩余的语句(直接结束当前循环)。(break语句也在switch语句中使用)
continue 关键字:continue语句用在循环语句体中,用于终止某次循环过程(只退出当前循环,继续下一次循环),即跳过循环体中尚未执行的语句接着进行下一次是否执行循环的判定
return关键字:用在方法体中,当执行return时,表示此方法已经运行完毕,可用于退出方法体(它是最绝的,无情。)
goto关键字
goto关键字很早就在程序设计语言中出现。尽管goto仍是Java的一个保留字,但并未在语言中得到正式使用;Java没有goto。然而,在break和continue这两个关键字的身上,我们仍然能看出一些goto的影子---带标签的break和continue.
“标签”是指后面跟一个冒号的标识符,例如: label:
对Java来说唯一用到标签的地方是在循环语句之前。而在循环之前设置标签的唯一理由是: 我们希望在其中嵌套另个循环,由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方.带标签的coutinue和break应用:
//求出100 - 110 的所有质数。(质数:只能被1和它本身整除的数)
public static void main(String[] args) {
int count = 0;
outer:for (int i = 100; i <= 110; i++) {
for (int j = 2; j < i/2; j++) {
if(i%j==0){
continue outer; //continue有outer标签的for循环(break用法类似)
}
}
System.out.println(i+" ");
}
}
四、方法
什么是方法?Java方法是代码语句的集合,它们在一起执行一个功能。
方法是解决一类问题的步骤的有序组合,包含于类或对象中,在程序中被创建,在其他地方被引用。
设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成1个功能,这样利于我们后期的扩展。
方法的命名规则:首字母小写,符合驼峰规范的标识符
方法的定义:方法包含一个方法头和一个方法体。
方法定义的基本格式:修饰符 返回值数据类型 方法名(参数列表){ 方法体;return 返回值; }
修饰符:修饰符,这是可选的,告诉编译器如何调用该方法,定义了该方法的访问类型。
返回值类型:方法可能会返值,return ValueType 是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值,在这种情况下,return ValueType 是关键字void。
方法名:是方法的实际名称。方法名和参数表共同构成方法签名,方法名和参数列表唯一确定一个类中的方法。
参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。
- 形式参数:在方法被调用时用于接收外界输入的数据。
- 实际参数:实参调用方法时实际传给方法的数据。
方法体:定义该方法的功能;使用关键字return来返回方法的返回值。
方法的调用
调用方法:对象名.方法名(实参列表)
Java 支持两种调用方法的方式,根据方法是否返回值来选择
当方法返回一个值的时候,方法调用通常被当做一个值。例如:int larger = max(30,40);
如果方法返回值是void,方法调用一定是一条语句。例如:System.out.println("Hello,kuangshen!");//方法使用举例
package first;
public class Shh {
/**
* @param n
* @return int
*/
public static int sum(int n){ //1.定义了一个方法,求1-n相加的值。
int result = 0;
for (int i = 1; i <= n; i++) { //2.求值的方法体
result += i;
}
return result; //3.返回int型结果
}
public static void main(String[] args) {
int i = 100;
int sum = sum(i); //4.调用sum方法。求1-100的和。
System.out.println(sum);
}
}
方法的参数传递:JAVA中只有值传递,没有引用传递。无论是基本类型的还是引用类型的参数传递都是值传递。
方法的重载
什么是重载?重载就是在一个类中,有相同的函数名称,但形参不同的函数
方法的重载的规则:
方法名称必须相同。参数列表必须不同 (个数不同、或类型不同、参数排列顺序不同等)
方法的返回类型可以相同也可以不相同。
仅仅返回类型不同不足以成为方法的重载
重载的实现原理:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。
public class Shh {
//定义了三个重载的方法
public static int add(int a,int b){return a+b;}
public static int add(int a,int b,int c){return a+b+c;}
public static int add(int a,int b,int c,int d){return a+b+c+d;} public static void main(String[] args) {
int a=1,b=1,c=1,d=1;
int add = add(a, b);
int add1 = add(a, b, c);
int add2 = add(a, b, c, d); //分别调用了三个重载的方法
System.out.println(add);
System.out.println(add1);
System.out.println(add2);
}
}
命令行传参,也就是main方法传参(不常用)
有时候你希望运行一个程序时,顺便再传递给它消息。这要靠传递命令行参数给main函数实现
例如:
public class Shh {
public static void main(String[] args) {
for (String s:args){ //打印main方法中的args的数据。
System.out.println(s);
}
}
}
传入参数时,会被main方法的args数组接收
可变参数:用于需要传入不确定个数的统一类型的数据的情景。它本质上就是一个数组
定义方式:在方法声明中,在指定参数类型后加一个省略号(...)
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前申明。public class Shh {
public static void main(String[] args) {
int add = add(1, 2, 3);
int add1 = add(1, 2, 3, 4, 5);
System.out.println(add);
System.out.println(add1);
}
//定义了一个带有可变参数 num 的方法
public static int add(int ...num){ //可变参数:num,它相当于一个数组。
int result = 0;
for (int i = 0; i < num.length; i++) { //遍历数组,计算结果。
result += num[i];
}
return result;
}
}
方法还可以抛出异常,见后面章节。
递归
递归是啥?递归就是方法A自己调用自己。
利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
递归结构包括两个部分:递归头和递归体
递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。递归体:什么时候需要调用自身方法public class Shh {
public static void main(String[] args) {
int myl = myl(5);
System.out.println(myl);
}
//计算n的阶层
public static int myl(int n){
if(n>0){ //递归体
return n*myl(n-1);
}else return 1; //递归头
}
}
五、数组
数组的概述:
- 数组是相同类型数据的有序集合。
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。
- 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们.
数组的声明和创建
数组的声明:首先必须声明数组变量,才能在程序中使用数组。
下面是声明数组变量的语法
首选的方法:dataType[] arrayName;
效果相同,但不是首选方法:dataType arrayName[];
创建了一个数组,也就是在内存中开辟了一个内存空间,数组名就指向了这个内存空间,arrayName[n]指向了这个空间中的某一部分。
数组的创建:Java语言使用new操作符来创建数组,语法如下:
dataType[] arrayRefVar = new dataType[arraySize]; **
(数组索引从0开始数组的元素是通过索引访问的**)数组的声明和创建的内存分析
数组的初始化:
静态初始化(创建+赋值):datatype []arrName = {data1,data2,data.....}; 或者 datatype []arrName = new datatype[] { data1,data2,data..... };(必须在声明的同时初始化)
动态初始化:datatype []arrName = new datatype[size]; (可以先声明,再初始化,动态初始化之后,数组中元素就自动被赋予了该数据类型的默认值。)
//静态初始化:下方的写法直接将data数组初始化为长度为 5 的数组,必须在声明的同时初始化。
int [] data = {1,2,3,4,5};
//动态初始化:
int []data = new int[5]; //声明并初始化
int []data01 = null; //先声明后初始化
data01 = new int[5];
数组的四个基本特点:
其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。下标的合法区间,[0,length-1],如果越界就会报错:ArrayIndexOutOfBoundsException (数组下标越界异常)
元素必须是相同类型,不允许出现混合类型。
数组中的元素可以是任何数据类型,包括基本类型和引用类型。
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型数组对象本身是在堆中的
数组的使用:数组初始化之后,使用 arrName[下标] 来操作数组中的元素,注意下标从0开始。此外,数组也可以作为方法的返回值或者参数。与其他数据类型作为参数或返回值的用法是一致的。
//定义了一个有一个参数是数组,以及返回值为数组的方法。
public int[] getArr(int [] data){
return data;
}
//数组的遍历:
public static void main(String[] args) {
int []data = {1,2,3,4,5,6};
for (int i = 0; i < data.length; i++) { //普通的for循环遍历数组
System.out.println(data[i]);
}
for (int x : data) { //使用增强for循环遍历数组
System.out.println(x);
}
}
多维数组:数组的数组,可以理解为数组中的元素是另一个数组。
//二维数组的初始化也可以分为静态初始化和动态初始化
//静态初始化,定义一个三行三列的数组:int [][]data = {{1,2,3},{1,2,3},{1,2,3}};
int [][]data = new int[3][3]; //动态初始化,定义一个三行三列的数组
//在二维数组中,data[n]表示行数组,它是一个对象。
System.out.println(data[0].length); //输出3,因为每行有3个数据。
System.out.println(data[0]); //打印出[I@1b6d3586,对象的哈希值
//二维数组的使用:打印二维数组
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
System.out.println(data[i][j]);
}
}
n维数组原理跟二维数组是类似的。
Arrays类:java.util.Arrays,是一个数组的工具类,包含多种操作数组的方法,如果sort方法,进行数组排序、copys进行数组数据复制等等。(可参考JDK帮助文档)
六、面向对象编程
面向对象与面向过程编程。
面向过程:一种线性思维,步骤清晰简单,第一步做什么,第二步做什么....
面对过程适合处理一些较为简单的问题。面向对象思想:
物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索面向对象适合处理复杂的问题,适合处理需要多人协作的问题!对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
什么是面向对象?面向对象编程(Object-Oriented Programming,OOP)
面向对象编程的本质就是:以类的方式组织代码,以对象的方式组织(封装)数据
三大特性:封装、继承、多态
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
从代码运行角度考虑是先有类后有对象,类是对象的模板。
对于方法的补充
再次理解方法:什么是方法?方法定义在类中,表示这一个类的行为和功能,比如一个类 人 ,那么它就应该有许多方法:行走、吃饭、睡觉、尿尿等,表示人的行为能力。
静态方法和非静态方法:静态方法可以通过 类名.方法名 来调用,非静态方法必须通过 类对象.方法名 来调用。
package first;
public class Human { //定义了一个类,有一个静态方法,一个非静态方法
public static void speak(){
System.out.println("你好,憨憨~");
}
public void talk(){
System.out.println("你好,靓女!");
}
} package first;
public class Shh {
public static void main(String[] args) {
Human.speak(); //调用静态方法
Human human = new Human();
human.talk(); //调用非静态方法
}
}
注意:在同一个类中,非静态方法可以调用静态方法,静态方法不能调用非静态方法!因为静态方法是随类的存在而存在的,而非静态方法跟随类对象的存在而存在。
形参和实参
值传递和引用传递:java中都是值传递,没有引用传递。
在方法中,this关键字代表当前类对象。
类与对象
类:类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。比如:动物、植物、手机、电脑......
Person类、Pet类、Car类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为(类包括类属性和类方法,类属性是类静态的特征,类方法是类动态的行为)
public class Human {
String name; //类的属性
public void eat(){ //类方法
System.out.println(this.name+"吃饭饭");
}
}
注意:一个JAVA文件中可以定义多个类,但是只能有一个可以使用 public 修饰符修饰
类五大成分:成员变量、构造器,方法,代码块,内部类
对象是抽象概念的具体实例。张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例。
对象能够体现出类特点,展现出功能的是具体的实例,而不是一个抽象的概念
对象的创建和初始化
对象的创建:使用new关键字创建对象,基本格式为:Class className = new Class(参数); (实际上就是调用了类的构造器)
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及调用类中构造器。
package first;
public class Human {
String name; //类的属性
int age; //类的属性
public void eat(){ //类的方法 吃饭
System.out.println(this.name+"吃饭饭");
}
public void sleep(){ //类的方法 睡觉
System.out.println(this.name+"睡觉觉");
}
} package first;
public class Shh {
public static void main(String[] args) {
Human human = new Human(); //创建一个对象
human.age = 10; //设置对象的属性。
human.name = "反正不是小明";
while (true){ //反正不是小明陷入了睡觉吃饭的死循环。
human.sleep(); //调用对象方法
human.eat(); //调用对象方法
}
}
}
构造器:可以用于初始化一个类的对象,并返回对象的地址。
一个类必然带有一个构造器,默认构造器是一个方法体为空的以类名为方法名的方法
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下几个特点:
必须和类的名字相同
必须没有返回类型,也不能写void
构造器可以带有参数,也可以实现方法的重载。当定义了有参构造器之后,就必须显示地定义无参构造器!!否则这个类就没有无参构造器了。
(在声明一个类的时候,一般都会定义有参构造和无参构造)
构造器的作用:在new一个对象的时候,会自动去执行构造器,那么构造器就可以用于初始化类对象的属性。使用new关键字的本质就是调用构造器。
package first;
public class Human {
String name; //类的属性
int age; //类的属性
public Human(){} //无参构造器
public Human(String name){this.name = name;} //有参构造器
public Human(String name,int age){
this.age = age;
this.name = name;
}
}
this关键字:this代表当前对象的地址。
创建对象的内存分析
package first;
//1.定义了一个类
public class Pet {
String name; //类的属性
int age; //类的属性
public Pet(){}
public void shout(){
System.out.println(this.name+"叫");
}
} //
package first;
public class Shh {
public static void main(String[] args) {
Pet dog = new Pet();
pet.age = 10;
pet.name = "反正不是小明";
pet.shout();
}
}
面向对象的三大特点 ----- 封装
我们程序设计要追求“高内聚,低耦合”。
- 高内聚:就是类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅暴露少量的方法给外部使用。
封装的原则:对象代表了什么,就得封装对应的数据,并提供数据对应的行为。
封装(数据的隐藏):通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
( 类属性私有private,使用get/set方法访问类数据)(加入private修饰符之后,该属性就属于对象私有,只能被对象内部的方法使用)
package first;
public class Pet { //定义了一个类
private String name; //类的属性
private int age; //类的属性
public Pet(){}
public void shout(){System.out.println(this.name+"吃东西");}
public String getName() {return name;} //get set方法,对外暴露,用于访问对象数据。
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
} package first;
public class Shh {
public static void main(String[] args) {
Pet human = new Pet();
human.setAge(10); //只能通过对象的set方法来修改属性值
int age = human.getAge(); //只能通过对象的get方法来获取数据
}
}
封装的意义:提高程序安全性,保护数据;隐藏代码的实现细节;统一接口;提高系统可维护性;
标准 JavaBean,也就是实体类,其对象可以用于在程序中封装数据
- 标准JavaBean的书写要求:
成员变量使用private 修饰
提供对成员变量的访问方法,getXXX,setXXX 方法
必须提供一个无参构造函数,有参构造可有可无。
面向对象的三大特点 ----- 继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模,extands的意思是“扩展”。子类是父类的扩展。
JAVA中类只有单继承,没有多继承!也就是一个类只能有一个直接父类,但是一个类可以有多个子类。
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示子类和父类之间,从意义上讲应该具有”is a”的关系.
子类将继承父类的所有方法和属性,包括静态方法和属性,私有的(被private修饰的)属性和方法是不能继承的。(其实是可以继承的,但是子类不能直接访问父类私有的内容而已)子类不能继承父类的构造器。
Object 类:是所有类的父类,在JAVA中所有的类都直接或者间接地继承Object类。
继承的设计规范:子类们相同特征(共性属性,共性方法)放在父类中定义,子类
类独有的的属性和行为应该定义在子类自己里面。继承的内存分析:
在子类中访问成员满足:就近原则。先子类局部范围找;然后子类成员范围找;然后父类成员范围找,如果父类范围还没有找到则报错。也就是说如果子父类中,出现了重名的成员,会优先使用子类的,此时,如果一定要在子类中使用父类的怎么办?
可以通过super关键字,指定访问父类的成员。
super关键字
使用方式:super.属性(调用父类属性) 或 super.方法(调用父类方法) 或 super() (调用父类无参构造) 或 super(参数列表)(调用父类有参构造)
作用:可以用于在子类中明确调用父类的属性和方法。所以必须在子类的构造器或者普通方法中使用(注意和this关键字对比)
//定义了一个类 People ,有两个属性,一个是私有属性。
public class People {
String name = "shaZi";
private String age;
}
//定义了一个类继承 People类
public class Student extends People{
String name = "傻子";
public void test(String name){
System.out.println(name); //使用传方法参数 name
System.out.println(this.name); //使用this关键字,调用本类的属性name
System.out.println(super.name); //使用super关键字,调用父类的属性name
//注意父类的私有属性 age,无法使用super.age来访问。
}
}
//测试
public class Shh {
public static void main(String[] args) {
Student student = new Student();
//调用student类的test方法。
student.test("stupid"); //执行结果:分别输出 stupid、傻子、shaZi
}
}
super关键字调用构造器:
//People类
public class People {
public People(){
System.out.println("People类的构造器");
}
}
//定义了一个Student类,继承People类。
public class Student extends People{
public Student(){
System.out.println("Student类的构造器");
}
}
//测试:
public class Shh {
public static void main(String[] args) {
Student student = new Student(); //
}
}
运行结果:依次输出 “people类的构造器”、“Student类的构造器”
理解:在创建一个类的对象时,会调用其构造器;而在一个类的构造器中,会默认在第一行先调用父类的构造器( super() )
super调用父类构造器的作用是什么?通过调用父类有参数构造器来初始化继承自父类的数据
手动使用super()来调用父类的构造器时,必须在第一行调用,所以如果同时继承两个类的话会报错!!所以:
//定义了一个Student类,继承People类。
public class Student extends People{
public Student(){
System.out.println("Student类的构造器");
}
}
//上方代码与下方代码等同:
public class Student extends People{
public Student(){
super();
System.out.println("Student类的构造器");
}
}
注意:若 父类的构造器带有参数,无参构造器被覆盖了,这时候使用 super(参数列表) 来调用父类构造器。依然必须在构造器中第一行调用。
与 this关键字的比较:
- 两者代表的对象不同,this代表本身调用者这个对象;super代表父类对象
- this没有继承也可以使用,super只能在继承条件才可以使用构造方法;this() 调用本类的构造,super()调用父类的构造。
方法重写(跟方法的重载是两码事):重写需要有继承关系,子类重写父类的方法
在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称
子类这个方法是重写的方法。方法重写的应用场景:当子类需要父类的功能,但父类的该功能不完全满足自己的需求时,子类可以重写父类中的方法。
注意:子类和父类的方法名和参数列表必须相同;修饰符范围可以扩大但不能缩小(public>Protected>Default>private);抛出的异常范围,可以被缩小,但不能扩大;
哪些方法不能被重写?用static修饰符修饰的静态方法,用final修饰的方法,private修饰的私有方法。
public class People {
public void test(){
System.out.println("People的方法");
}
}
//继承People类
public class Student extends People{
@Override //此注解表示重写方法。是一个有功能的注解。
public void test(){ //重写父类的test方法
System.out.println("Student的方法");
}
}
@Override重写注解:@Override是放在重写后的方法上,作为重写是否正确的校验注解。
加上该注解后如果重写错误,编译阶段会出现错误提示。建议重写方法都加@Override注解,代码安全,优雅!
面向对象的三大特点 ----- 多态
多态:同类型的对象,执行同一个行为,会表现出不同的行为特征。一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类引用指向子类对象)
(注意:多态是方法的多态,属性没有多态性)
多态存在的条件:有继承关系、子类重写父类方法、父类引用指向子类对象
多态的常见形式:
//一个动物类
public abstract class Animal {
public String v = "父类的属性";
public abstract void run();
}
//狗
public class Dog extends Animal {
public String v = "狗的属性"; @Override
public void run() {
System.out.println("你看那人跑得好像一条狗");
}
}
//猫
public class Cat extends Animal {
public String v = "猫的属性";
@Override
public void run() {
System.out.println("猫在撒欢,猫的快乐。");
}
}
//测试:
public static void main(String[] args) {
Animal animal1 = new Cat();
Animal animal2 = new Dog();
//方法:编译看左边,运行看右边
animal1.run(); //输出“猫在撒欢,猫的快乐。”
animal2.run(); //输出“你看那人跑得好像一条狗” //属性:编译看左边,运行看左边
System.out.println(animal1.v); //输出“父类的属性”
System.out.println(animal2.v); //输出“父类的属性”
}
假设有 Person类 和 Student类,Student 为 Person类的子类
一个对象的实际类型是确定的:“new Student();”表示一个Student的实例对象;”new Person();“则表示一个Person类的实例对象。
而可以指向的引用类型就不确定了,可以使用父类的引用指向子类:”Person person = new Student();“,父类的引用指向子类的对象。
”Student student = new Student();“,此时student能调用的方法只有自己本身的方法,以及从父类继承的方法。
”Person person = new Student();“。此时person只能调用Person类本身的方法,以及被Student子类重写的方法,不能调用Student子类独有方法。
(对象能执行哪些方法,主要看对象左边的类型,和右边关系不大!)
多态中成员访问的特点:
方法调用:编译看左边,运行看右边。
变量调用:编译看左边,运行也看左边。(多态侧重行为多态)
多态的优势:
多态中存在的问题?多态下不能使用子类的独有功能。因为编译看左边,父类类型中并没有子类中的功能,所以编译之后,就无法使用子类的独有功能了。
如何解决多态的问题?类型转换
自动类型转换:(从子到父)子类对象赋值给父类类型的变量指向。
强制类型转换:(从父到子) 子类 对象变量 = (子类)父类类型的变量
(由此可以解决多态下的劣势,可以实现调用子类独有的功能。)
//一个动物类
public abstract class Animal {
public String v = "父类的属性";
public abstract void run();
}
//猫
public class Cat extends Animal {
public String v = "猫的属性";
@Override
public void run() {
System.out.println("猫在撒欢,猫的快乐。");
}
//子类独有的方法:
public void climb(){
System.out.println("猫会上树~");
}
}
//测试:
public static void main(String[] args) {
Animal animal = new Cat(); //自动类型转换
animal.run(); //只能调用父类的方法以及子类重写的方法。
Cat cat = (Cat)animal; //强制类型转换
cat.climb(); //可以使用子类独有的方法
}
instanceof关键字:用于判断一个对象是什么类型的。(理解下方运算结果)
如果实例是某个类
//有一个Person类,Student类 和 Teacher类是它的子类。Object 类是所有类的祖宗类。
//0bject > Person > Student
//0bject > Person > Teacher Object object = new Student();
System.out.println(object instanceof Student); //true
System.out.println(object instanceof Person); //true
System.out.println(object instanceof Object); //true
System.out.printIn(obiect instanceof Teacher); //False
System.out.println(object instanceof String); //False Person person = new Student();
System.out.printIn(person instanceof Student);//true
System.out.printIn(person instanceof Person);//true
System.out.println(person instanceof Object);//true
System.out.println(person instanceof Teacher); //False
//System.out.println(person instanceof String); 编译报错 Student student = new student():
System.out.println(student instanceof Student); //true
System.out.printIn(student instanceof Person); //true
System.out.printIn(student instanceof Object); //true
//System.out.println(student instanceof Teacher); //编译报错
//System.out.println(student instanceof String); //编译报错
理解 “实例a instanceof 类b” :实例a 必须与 类b在同一颗继承树上,否则不能进行比较,会产生编译错误。类的实例包含本身的实例,以及所有直接或间接子类的实例。
static 关键字
static 是静态的意思。static修饰成员变量表示该成员变量在内存中只存储一份,可以被共享访问、修改。
static关键字的作用:static关键字是一个修饰符,可用于修饰变量或者方法;被它修饰的变量或者方法的生存周期与所在类等同,直接使用 类名.方法/变量 访问,无需通过实例对象。
什么时候要将方法定义为静态方法?
表示对象自己的行为的,且方法中需要直接访问实例成员,则该方法必须申明成实例方法。
如果该方法是以执行一个通用功能为目的,或者需要方便访问,则可以申明成静态方法
静态变量的内存机制:当一个类的成员变量 v1 使用static 修饰之后,当使用这个类时,会将这个类的信息加载到方法区中,同时会在堆内存中开创一个该类的静态变量区(注意是同步发生的),然后将该类的静态成员变量放入该静态变量区中。
public class AClass {
public static int v1;
private int v2;
}
//理解:
public static void main(String[] args) {
AClass.v1 = 1;
AClass class01 = new AClass();
AClass class02 = new AClass();
System.out.println(AClass.v1); //1
System.out.println(class01.v1); //1
System.out.println(class02.v1); //1
class01.v1 = 2; //通过class01对象修改 v1的值
System.out.println("============]");
System.out.println(AClass.v1); //2
System.out.println(class01.v1); //2
System.out.println(class02.v1); //2
}
静态方法的内存机制:当一个类的成员方法使用static 修饰之后,当使用这个类时,会将这个类的信息加载到方法区中,与此同时会将静态方法加载到方法区内,当使用 类名.静态方法名 调用静态方法时,会直接通过类名在方法区中找到这个静态方法!(而实例方法则需要通过对象来调用。)
static访问的注意事项:
静态方法只能访问静态的成员,不可以直接访问实例成员(因为静态方法被加载的时候,实例成员还未被jvm加载出来,所以无法访问到)
实例方法可以访问静态的成员,也可以访问实例成员
静态方法中是不可以出现this关键字
static关键字的应用一 ------ 代码块(代码块是类的5大成分之一,类五大成分:成员变量、构造器,方法,代码块,内部类)
package first;
public class Student extends People{
{ System.out.println("匿名代码块"); } //匿名代码块
static { System.out.println("静态代码块"); } //静态代码块
public Student(){
System.out.println("构造方法"); //构造器
}
}
//============================================================================
public static void main(String[] args) {
Student student = new Student();
System.out.println("===================================");
Student student1 = new Student();
}
/* 执行结果:
静态代码块
匿名代码块
构造方法
===================================
匿名代码块
构造方法
*/
理解:1.实例一个类的时候,会先执行静态代码块,然后执行匿名代码块,再执行构造器。 2.在一个类的生存周期中,静态代码块只会执行一次 3.匿名代码块每次实例化对象时,都会执行一次。 4.代码块一般用于初始化数据。为变量赋初值等。
static关键字的应用二 ------ 工具类
工具类是什么,有什么好处?内部都是一些静态方法,每个方法完成一个功能一次编写,处处可用,提高代码的重用性
工具类有什么要求?建议工具类的构造器私有化处理,因为工具类无需进行实例化,只需要提供方法即可。
static关键字的应用二 ------ 单例模式
初识设计模式:开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式。
设计模式 ---- 单例模式:可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。
例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间。单例模式可以有很多实现方式,如饿汉单例模式、懒汉单例模式等。饿汉单例模式:在用类获取对象的时候,对象已经提前为你创建好了
实现步骤:1.定义一个类,把构造器私有。(构造器私有之后,就无法被调用,也就无法再创建对象了)2.定义一个静态变量存储一个对象。(对外提供一个对象,实现单例模式)
//定义了一个只能创建一个对象的类
public class SingleInstance {
public static SingleInstance singleInstance = new SingleInstance(); //对外提供一个对象
private SingleInstance(){} //私有化构造器
} public static void main(String[] args) {
SingleInstance singleInstance01 = SingleInstance.singleInstance;
SingleInstance singleInstance02 = SingleInstance.singleInstance;
System.out.println(singleInstance01 == singleInstance02);
//输出true,说明这俩引用指向了一个对象。
}
懒汉单例模式:在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
实现步骤:1.定义一个类,把构造器私有。2.定义一个静态变量存储一个对象。3.提供一个返回单例对象的方法
//定义了一个只能创建一个对象的类
public class SingleInstance {
public static SingleInstance singleInstance; //对外提供一个对象
private SingleInstance(){} //私有化构造器
//对外提供一个方法,实例化一个对象。
public static SingleInstance getInstance(){
if(singleInstance == null){
singleInstance = new SingleInstance();
}
return singleInstance;
}
} public static void main(String[] args) {
SingleInstance singleInstance01 = SingleInstance.getInstance();
SingleInstance singleInstance02 = SingleInstance.getInstance();
System.out.println(singleInstance01 == singleInstance02);
//输出true,说明这俩引用指向了一个对象。
}
抽象类
abstract 修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
注意:
抽象类中可以有普通的方法,可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
抽象类,不能使用new关键字来创建对象,它是用来让子类继承的
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的
子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类
//定义一个抽象类
public abstract class AbstractClass {
public abstract void test(); //抽象方法
public void sout(){
System.out.println("输出点啥");
}
}
// 定义一个类继承抽象类
public class Son extends AbstractClass{
@Override
public void test() { //实现抽象方法
System.out.println("说点好听的");
}
}
抽象类的使用场景:
抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承。
当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又
不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成。此时这个类就可以声明成抽象类。
抽象类的应用:另一个设计模式 “模板方法模式”
使用场景:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。
实现步骤:1.把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。 2.模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
案例
不使用设计模式:两个类的write方法中,大部分内容是冗余的,只有正文部分是不一样的。public class ChildrenStudent {
public void write(){
System.out.println("《我的父亲》");
System.out.println("介绍一下我的父亲");
System.out.println("我的父亲很牛逼"); //正文部分
System.out.println("这就是我的父亲");
}
}
public class MiddleStudent {
public void write(){
System.out.println("《我的父亲》");
System.out.println("介绍一下我的父亲");
System.out.println("我的父亲不牛逼,但是很好,很善良。"); //正文部分
System.out.println("这就是我的父亲");
}
}
使用模板方法模式:
//定义一个抽象类,将正文部分抽象出来。
public abstract class Student {
public void write(){ //这个方法就是模板方法
System.out.println("《我的父亲》");
System.out.println("介绍一下我的父亲");
System.out.println(text()); //正文部分
System.out.println("这就是我的父亲");
}
public abstract String text();
}
//中学生类:继承Student,实现获取正文的方法。
public class MiddleStudent extends Student{
@Override
public String text() {
return "\"我的父亲不牛逼,但是很好,很善良。\"";
}
}
//小学生类:继承Student,实现获取正文的方法。
public class ChildrenStudent extends Student{
@Override
public String text() {
return "\"我的父亲很牛逼\"";
}
}
依照模板方法模式的思想,模板方法就是提供一个模板,那么它就是不能被重写的,那就模板方法一般使用 final 修饰
接口 interface
普通类抽象类,接口对比:
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法) 都有!
接口:只有规范!自己无法写方法~是一种专业的约束! 约束和实现分离:(面向接口编程~)
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是...则必须能...”的思想。如果你是天使则必须能飞;如果你是汽车,则必须能跑;如果你好人,则必须干掉坏人;如果你是坏人,则必须欺负好人....
接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
面向对象的精髓,是对对象的抽象,最能体现这一点的就是接口。(为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、iava、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。)
注意点:因为接口代表一种规范,所以它需要被公开,被暴露。
接口中定义的成员变量默认使用 public static final 来修饰,都是常量
接口中的方法默认使用 public abstract 来修饰,那么实现接口的类就必须要重写实现这些方法。
接口的定义:与普通类的定义一样, 将 class 关键字换成interface关键字即可。
接口的使用:接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。使用实现类使用 implements 来实现接口。一个类可以实现多个接口。(只能继承一个类,“单继承,多实现”)
//一般格式:public class 类名 extends 类名 implements 接口1,接口2...{ }
//定义了一个接口
public interface FirstInterface {
int member01 = 20; //接口中定义的成员变量默认使用 public static final 来修饰,都是常量
String member02 = "member";
void sayHello(String name); //接口中的方法默认使用 public abstract 来修饰
void sayGoodbye(String name);
} //定义了一个类实现接口
public class User implements FirstInterface{
@Override
public void sayHello(String name) { //必须重写接口中的方法
System.out.println(name + "hello");
}
@Override
public void sayGoodbye(String name) {
System.out.println(name + "goodbye"); //必须重写接口中的方法
}
}
接口与接口的关系:一个接口可以同时继承多个接口。
public interface SecI {}
public interface ThrI {}
//同时继承两个接口
public interface FirI extends SecI,ThrI {}
JDK8开始之后,接口新增一些新特性,新增了一些可定义的方法。
为什么要新增这些方法?一种业务场景,项目Version 1.0,使用定义了一些接口规范,然后通过一些类成功实现,项目Version 1.0成功上线。项目Version 2.0需要对项目Version 1.0进行升级,需要丰富接口的内容,那么此时所有的实现类都要去实现新增的接口内容,就会造成很大的开发代价。所以为了能在丰富接口功能的同时,又不对子类代码进行更改,就允许接口中直接定义带有方法体的方法。
新增的第一种方法:默认方法。类似之前写的普通实例方法,必须用default修饰;默认会public修饰,需要用接口的实现类的对象来调用。
新增的第二种方法:静态方法。默认会使用public修饰,必须static修饰。
需要注意的是,接口的静态方法必须用本身的接口名来调用。新增的第二种方法:私有方法。就是私有的实例方法:,必须使用private修饰,从JDK 1.9才开始有的;只能在本类中被其他的默认方法或者私有方法访问。
public interface SportManInter {
/**
1、JDK 8开始 : 默认方法(实例方法)
-- 必须default修饰,默认用public修饰
-- 默认方法,接口不能创建对象,这个方法只能过继给了实现类,由实现类的对象调用。
*/
default void run(){
System.out.println("跑的很快~~~");
}
/**
2、静态方法
必须使用static修饰,必须使用接口名来调用。
*/
pubic static void inAddr(){
System.out.println("我们都在学习Java新增方法的语法,它是Java源码自己会用到的~~~")
}
/**
3、私有方法(实例方法)
-- JDK 1.9开始才支持的。
-- 必须在接口内部才能被访问
*/
private void go(){
System.out.println("开始跑~~~");
}
使用接口的注意事项。
接口不能创建对象
一个类实现多个接口,多个接口中有同样的静态方法不冲突。因为静态方法只能使用接口名来调用。
一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
内部类
什么是内部类?内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了
内部类的分类:1.成员内部类; 2.静态内部类; 3.局部内部类; 4.匿名内部类
成员内部类:
- 定义:
public class OuterClass {
private int id; //成员变量
public void out(){ //外部类方法
System.out.println("外部类的方法");
}
public class inner{ //成员内部类
int member; //成员内部类的变量
public void in(){ //成员内部类方法
System.out.println("内部类的方法");
System.out.println(id); //可以访问外部类的私有成员变量。
}
}
}
- 使用:先实例化外部类,再通过外部类的实例实例化内部类。(成员内部类可以访问外部类中的所有的成员变量或者方法)
成员内部类:public class Shh {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass(); //实例化外部类
OuterClass.inner inner = outerClass.new inner(); //通过外部类的实例实例化内部类
inner.in(); //通过内部类实例,调用方法。
}
}
- 一道面试题:
- 定义:
静态内部类:就是使用static修饰的成员内部类。因为它是静态的,所以不能访问外部类中的非静态成员变量或者方法。
- 定义:
public class OuterClass {
private int id; //成员变量
public void out(){ //外部类方法
System.out.println("外部类的方法");
}
public static class inner{ //静态内部类
int member; //静态内部类的变量
public void in(){ //静态内部类方法
System.out.println("内部类的方法");
}
}
}
- 使用:创建静态内部类的实例不需要通过外部类的实例。
public class Shh {
public static void main(String[] args) {
OuterClass.inner inner = new OuterClass.inner(); //创建静态内部类的实例。
inner.in();
}
}
- 定义:
局部内部类:定义在方法中的内部类。
- 定义:局部内部类,不能使用public、protected、private、static 来修饰!!!
public class OuterClass {
private int id; //成员变量
public void out(){ //外部类方法
class inner{ //局部内部类,不能使用public、protected、private、static 来修饰!!!
int member; //局部内部类的变量
public void in(){ //局部内部类方法
System.out.println("内部类的方法");
}
}
}
}
- 使用:局部内部类中能在当前方法中使用,直接使用new关键字实例化即可。
- 定义:局部内部类,不能使用public、protected、private、static 来修饰!!!
匿名内部类:本质上是一个没有名字的局部内部类,定义在方法中、代码
块中等。作用:方便创建子类对象,最终目的为了简化代码编写。
//定义一个类
public class AClass {
public void method(){
System.out.println("this is a method");
}
}
//使用匿名内部类调用method方法
public static void main(String[] args) {
new AClass().method(); //匿名内部类。
}
//定义一个接口
public interface AInterface {
void test(String name);
}
//使用匿名内部类实现接口
public class Shh {
public static void main(String[] args) {
AInterface aInterface = new AInterface() { //匿名内部类实现接口,可以省略一个实现类文件
public void test(String name) { //重写实现接口中的方法
System.out.println(name + "说点啥");
}
};
aInterface.test("shh");
}
}
特点总结:
1.匿名内部类是一个没有名字的内部类。
2.若名内部类写出来就会立即产生一个匿名内部类的对象。
3.匿名内部类的对象类型相当于是当前new的那个的类型的子类型。
4.匿名内部类也会产生class文件。
权限修饰符和final修饰符
什么是权限修饰符:是用来控制一个成员能够被访问的范围。可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
四种权限修饰符:pulbic、default、protected、private
final的作用:final 关键字是最终的意思,可以修饰(类、方法、变量)
修饰类:表明该类是最终类,不能被继承。
修饰方法:表明该方法是最终方法,不能被重写。
修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次,也就是被它修饰的变量,定义之后就必须赋值,且之后不可再修改)。
final修饰变量的注意:
final修饰的变量是基本类型:那么变量存储的数据值不能再发生改变
final修饰的变量是引用类型:那么变量存储的地址值不能再发生改变
变,但是地址指向的对象内容是可以发生变化的。
枚举
枚举的概述:枚举是Java中的一种特殊类型。其作用是:"为了做信息的标志和信息的分类"。
定义枚举的格式:修饰符 enum 枚举名称{第一行都是罗列枚举类实例的名称。}
//定义了一个枚举,有四个实例对象。
public enum FirEnum {
//1.枚举类的第一行必须罗列枚举类的对象名称,建议全部大写。
SPRING,SUMMER,AUTUMN,WINTER; //四个实例对象。
}
反编译的技巧:将java文件使用javac命令编译为class文件,然后再使用javap 命令将class文件进行反编译,即可。
将上面的枚举反编译之后得到:public final class FirEnum extends java.lang.Enum<FirEnum> {
public static final FirEnum SPRING;
public static final FirEnum SUMMER;
public static final FirEnum AUTUMN;
public static final FirEnum WINTER;
public static FirEnum[] values();
public static FirEnum valueOf(java.lang.String);
static {};
}
可得:
枚举类都是继承了枚举类型:java.lang.Enum
枚举都是最终类,不可以被继承。
构造器都是私有的,枚举对外不能创建对象
枚举类的第一行默认都是罗列枚举对象的名称的。
枚举相当于是多例模式
七、异常Exception
什么是异常?实际工作中,遇到的情况不可能是非常完美的。软件程序在运行过程中,非常可能遇到各种问题,我们统称为异常,英文是:Exception,意思是例外。
异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等异常发生在程序运行期间,它影响了正常的程序执行流程。异常的分类:
检查性异常:最具代表性的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
运行时异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
错误ERROR:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如当栈溢出时,错误就发生了,它们在编译也检查不到的
JAVA的异常体系结构:Java把异常当作对象来处理,并定义一个基类 java.lang.Throwable 作为所有异常的超类。在JavaAPI中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception.
两大类异常 ERROE 和 EXCEPTION的区别:
ERROR:
Error类对象由 Java 虚拟机生成并地出,大多数错误与代码编写者所执行的操作无关
Java虚拟机运行错误(Virtual MachineError),这些异常发生时,Java虚拟机 一般会选择线程终止
还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError) 、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况
EXCEPTION:这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生
Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
java中如何捕获和抛出异常
异常处理使用的关键字:try、catch、finally、throw、throws
异常的捕获:try{ 要监控的代码 } catch(异常对象){ 产生此异常时的处理逻辑 } finally{ 无论是否发生异常都要干的事情 }
public static void main(String[] args) {
int a = 1,b = 0;
try { //监控区:会监控这段代码块中的代码是否发生异常
System.out.println(a/b);
} catch (Exception e) {
//捕获try代码块中的代码的异常,可以有多个catch,catch中的异常类范围应该有小到大,否则编译错误
System.out.println(e.getMessage());
} catch (Throwable t){
System.out.println(t.getMessage());
} finally { //无论是否发生异常都会执行finally代码块,一般用于释放资源等。
System.out.println("嘻嘻");
}
}
异常的抛出
在方法中主动抛出异常,使用 throw 关键字。
public void test(int a,int b) {
if(b == 0){
throw new ArithmeticException(); //主动抛出异常
}
System.out.println(a/b);
}
在方法外抛出异常:使用throws 关键字,也就是将异常抛出给方法调用者来处理,一般会使用try catch 来捕获处理。
public void test(int a,int b) throws ArithmeticException{ //抛出异常
System.out.println(a/b);
} public static void main(String[] args) {
try {
new Shh().test(1,0); //捕获方法中抛出的异常,并进行对应异常处理。
} catch (ArithmeticException e) {
System.out.println("异常了");
} finally {
}
}
自定义异常:使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤:
1.创建自定义异常类2.在方法中通过throw关键字抛出异常对象
3.如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明
4.要抛出给方法调用者的异常,继续进行下一步操作。在出现异常方法的调用者中捕获并处理异常
八、常用类(API)
什么是API?APl(Application Programming interface)应用程序编程接口。简单来说就是Java帮我们已经写好的一些方法,可直接使用。
8.0 Object类(老祖宗)、Objects类
一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。Object类的方法是一切子类都可以直接使用的,所以我们要学习Obiect类的方法。
Object中的常用方法:
public String toString():默认是返回当前对象在堆内存中的地址上的信息“类的全限名@内存地址”
package shh;
public class People { //定义一个People类
private 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; }
}
public static void main(String[] args) {
People people = new People();
System.out.println(people.toString());
//输出:shh.People@1b6d3586
}
理解:然而默认输出的信息是没有任何意义的,toString方法存在的意义就是被子类重写,自定义需要输出的有价值的信息。
public Boolean equals(Object o):默认是比较当前对象与另一个对象的
的地址是否相同,相同返回true,不同返回false理解:然而equal方法来比较两个对象的地址是否相同的功能与直接使用“ == ” 是一样的。equals方法存在的意义就是被子类重写,自定义两个对象进行比较的规则,比如String 类中的 equals方法就是对Object中equals方法的重写,可以进行字符串对象内容的比较。
Objects类
- Objects类概述:
- Objects类的常用方法
- Objects类概述:
8.1 String类与StringBuilder类
作用:用于存储字符串数据,操作该字符串。java程序中所有的字符串都是该类的对象。
String类特点:String类对象一旦创建,就不可改变了。而String变量的每次修改其实都是产生并指向了新的字符串对象。原来的字符串对象是没有改变的,所以称为不可变字符串。
字符串对象的创建,有两种方式:
方式一:直接使用 "" 定义。 String 变量名 = "字符串内容";
方式二:通过String类的构造器创建
//无参构造器:String();
String str2 = new String();
str2 = "abcd"; // String(String str); 传入一个字符串。
String str3 = new String("abcd"); // String(char[] arr); 传入一个字符数组,将数组转换为字符串
char [] charArr = new char[]{'a','b','c','d'};
String str4 = new String(charArr); // String(byte[] arr); 传入一个字节数组,将字节数组转换为字符串。
byte[] bytes = "abcd".getBytes(StandardCharsets.UTF_8);
String str5 = new String(bytes);
两种创建方式有什么区别?
以“” 方式给出的字符串对象,在字符串常量池中存储,而且相同的内容只会存储其中一份。(只要不是使用 "" 直接创建出来的对象都是放在堆内存中的)
使用构造器创建的对象,会存放在堆内存中
public static void main(String[] args) {
String str01 = "abc";
String str02 = "abc";
System.out.println(str01 == str02); //true
//分析:使用“”创建的对象放在字符串常量池中,并且只创建一个,此处str01 和 str02 指向同一个对象。 String str03 = new String("abc");
String str04 = new String("abc");
System.out.println(str03 == str04); //false
//分析 使用构造器创建的对象,会存放在堆内存中,此处str03,str04 是两个不同的对象。
}
String类常用方法
比较两个字符串是否相等:boolean equals(String str); boolean equalsIgnoreCase(String str); (忽略大小写)
(不可以使用 == 来进行比较,因为字符串变量中存储的是字符串的地址,而不是具体的值。使用 == 来对比,仅仅是对地址的对比,而不是字符串值的对比。)
public int Length(): 获取字符串的长度
public char charAt (int index):获取某个索引位置处的字符(可用于遍历字符串的每一个字符)
public char[] toCharArray():把字符串转换成字符数组
public String substring (int beginIndex,int endIndex) :截取内容,索引位置beginIndex 到 endIndex 前一个字符。
public String substring (int beginIndex):从当前索引一直截取到末尾
public String replace (String target, String replacement) :将字符串中所有的 target 子串替换为 replacement 子串
public boolean contains (String s):判断字符串中是否包含 s 字串
public boolean startsWiths (String prefix):字符串是否 以 prefix字串 开始
public String[] split(String s): 按照某个内容把字符串分割成字符串数组返回。
public boolean matches(String regexp): 判断字符串是否匹配指定的正则。
public String replaceAll(String regex,String newStr):按照正则表达式匹配的内容进行替换
public String[] split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
StringBuilder类
StringBuilder类的作用:是一个可变的字符串类,我们可以把它看成是一个对象容器。也就是说对StringBuilder对象进行操作不会产生新对象,始终只会在一个对象中进行操作,而不是像String对象那样。所以可以提高字符串的操作效率,如拼接、修改等。
StringBuilder对象的创建:
- public StringBuilder();创建一个空白的可变的字符串对象,不包含任何内容
- public StringBuilder(String str):创建一个指定字符串内容的可变字符串对象
常用方法:
public StringBuilder append(任意类型):添加数据并返回StringBuilder对象本身(还是原来的对象)
public StringBuilder reverse():将对象的内容反转
public int length():返回对象内容长度
public String toString():通过toString()就可以实现把StringBuilder转换为String
StringBuilder类进行字符串操作效率更高的原因:
String对象使用 “+” 拼接,会经过StringBuilder对象去进行字符串操作,也就会创建多余的对象,所以效率低下
Stringbuilder始终在一个对象上操作,所以效率高
8.2 集合
8.2.1 什么是集合?
什么是集合?集合和数组都是容器。用于存放数据。集合是Java中存储对象数据的一种容器,只能存放引用类型的数据。
集合和数组的对比
数组
数组定义完成并启动后,类型确定、长度固定。
在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位。
数组适合的场景:当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储。
集合:
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。(注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。)
集合非常适合做元素的增删操作。
使用场景:数据的个数不确定,需要进行增删元素的时候。
集合类体系结构
集合分为两大类:Collection 和 Map
Collection单列集合,每个元素(数据)只包含一个值。
Map双列集合,每个元素包含两个值(键值对)。
单例集合Collection
双列集合Map
集合对于泛型的支持:集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。(若要存入基本类型的数据,只能使用包装类)
//不指定泛型,可以存入任意类型的数据
Collection list = new ArrayList();
list.add("af");
list.add(2);
list.add(false);
System.out.println(list); //[af, 2, false]
//指定泛型,只能存入指定类型的数据。
Collection<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(2);
8.2.2 Collection集合
Collecion集合的常用api:Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
public boolean add(E e):把给定的对象添加到当前集合中
public void clear():清空集合中所有的元素
public boolean remove(E e):把给定的对象在当前集合中删除,如果有两个内容相同的对象,优先删除第一个。
public boolean contains(Object obj):判断当前集合中是否包含给定的对象
public boolean isEmpty():判断当前集合是否为空
public int size():返回集合中元素的个数。
public Object toArray():把集合中的元素,存储到数组中
Collection集合的遍历
方式一:迭代器
概述:迭代器遍历就是一个一个的把容器中的元素访问一遍。迭代器在Java中的代表是lterator,迭代器是集合的专用遍历方式。
创建对象:Collection类中有一个方法:Iterator iterator(),可以返回一个迭代器对象。
常用方法:
boolean hasNext():是否还有下一个元素
E next():返回下一个元素,并将指针往后移动一位。
使用示例:
Collection list = new ArrayList();
list.add("af");
list.add(2);
list.add(false);
//获取一个迭代器对象
Iterator iterator = list.iterator();
while (iterator.hasNext()){ //是否还有元素
System.out.println(iterator.next()); //获取元素
}
方式二:增强for循环:可用于遍历集合。
概述:
既可以遍历集合也可以遍历数组。
它是JDK5之后出现的,其内部原理是一个lterator迭代器,遍历集合相当于是迭代器的简化写法。
实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了lterable接口。
基本格式:
使用示例:
Collection list = new ArrayList();
list.add("af");
list.add(2);
list.add(false);
//使用增强for循环遍历集合。
for (Object o : list) {
System.out.println(o);
}
方式三:使用Collection类的方法 default void foreach (Consumer<? super T> action);
概述:通过foreach方法进行遍历,结合lambada表达式,可以简化代码
示例:
Collection list = new ArrayList();
list.add("af");
list.add(2);
list.add(false);
//遍历集合。
list.forEach(new Consumer() {
//accept方法中的参数会接收list中的一个个元素。
//方法体即是对元素的操作。
@Override
public void accept(Object o) {
System.out.println(o);
}
});
//下方代码与上方遍历集合的代码等同,使用lambada表达式简化。
list.forEach(o-> System.out.println(o));
Collection集合存储自定义类型数据
8.2.3 List系列集合
List集合特点
ArrayList、LinekdList:有序,可重复,有索引(索引从0开始)。
有序:存储和取出的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
List集合特有的方法(此外Collection集合有的方法它都继承了)
void add(int index,E element):在此集合中的指定位置插入指定的元素
E remove(int index):删除指定索引处的元素,通返回被删除的元素
E set(int index,E element):修改指定索引处的元素,返回被修改的元素
E get(int index):返回指定索引处的元素
List的实现类的底层数据结构
ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。
List集合的遍历方式:除了Collection章节介绍的三种方式之外,因为它具有索引,所以可以直接使用普通的for循环来遍历集合。
ArrayList集合底层原理
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。每次加入元素就是将数据保存在数组中。
当数组满了,就会将数组按1.5倍的长度创建一个新数组,然后将数据迁移进入新数组。
LinkedList集合底层原理
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
特有的API:
public void addFirst(E e):在该列表开头插入指定的元素
public void addLast(E e):将指定的元素追加到此列表的末尾
public E getFirst():返回此列表中的第一个元素
public E getLast():返回此列表中的最后一个元素
public E removeFirst():从此列表中删除并返回第一个元素
public E removeLast():从此列表中删除并返回最后一个元素
当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
哪些遍历存在问题?
迭代器遍历集合且直接用集合删除元素的时候可能出现。
增强for循环遍历集合且直接用集合删除元素的时候可能出现。(不可以使用这种方式来删除数据,因为有异常,且无法解决)
异常问题:因为迭代器每次取出数据之后,指针就会指向下一个数据,而删除数据会使后面的元素全部往前挪动一个位置,这就导致了删除元素出现漏删,这就出现了异常。
//删除所有的java元素。
List<String> list = new ArrayList<>();
list.add("java");
list.add("java");
list.add("C");
list.add("C");
list.add("C");
//将java元素全部删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("java")) {
list.remove(next);
}
}
//执行remove操作会发生异常:
//Exception in thread "main" java.util.ConcurrentModificationException
如何解决?使用迭代器对象的remove()方法来删除元素即可。该方法在删除了元素之后,会将迭代器的指针自动往后挪动一个位置,从而解决这个异常。
List<String> list = new ArrayList<>();
list.add("java");
list.add("java");
list.add("C");
list.add("C");
list.add("C");
//将java元素全部删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("java")) {
iterator.remove(next); //使用迭代器的remove方法。
}
}
8.2.4 泛型详解、Collections工具类
泛型概述
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式:<数据类型>;注意:泛型只能支持引用数据类型。
集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:统一数据类型。把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现
现的异常,因为编译阶段类型就能确定下来。泛型可以在哪些地方上使用?
类后面 ------ 泛型类
方法申明上 ------ 泛型方法
接口后面 ------ 泛型接口
自定义泛型类
概述
定义类时同时定义了泛型的类就是泛型类。
泛型类的格式:修饰符 class 类名<泛型变量>{ } 例如: public class MyArrayList {}
此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
作用:编译阶段可以指定数据类型,类似于集合的作用。
泛型类的核心思想:把出现泛型变量的地方全部替换成传输的真实数据类型
使用示例:自定义一个泛型类,MyArrayList,模拟ArrayList集合的添加数据、删除数据的功能。
//加入泛型<E>,定义一个泛型类
public class MyArrayList<E> {
public void add(E e){
System.out.println("加入数据"+e);
}
public void Remove(){
System.out.println("删除数据");
}
} public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("abc");
}
自定义泛型方法
泛型方法概述:
定义方法时同时定义了泛型的方法就是泛型方法。
泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列列表){}
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
使用示例:自定义一个泛型方法,给你任何一个类型的数组,都能返回它的内容。
//自定义泛型方法
public static <E> String toString(E[]e){
String result = "[";
for (E e1 : e) {
result+=" "+e1;
}
result+=" ]";
return result;
}
public static void main(String[] args) {
String arr01[] = {"a","b","c"};
Integer arr02[] = {1,2,3};
System.out.println(toString(arr01)); //可接收String类型数组
System.out.println(toString(arr02)); //可接收Integer类型数组 }
自定义泛型接口
概述:
使用了泛型定义的接口就是泛型接口。
泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
使用示例:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
//定义了一个操作数据的泛型接口,有增删改查的功能
public interface Data<E> {
void insert(E e);
E remove();
void update(E e);
E query();
}
//有两个角色:学生、老师
public class Teacher{}
public class Student {} //两个角色操作数据的实现类
//学生操作数据的实现类。老师操作数据的实现类类似。
public class StudentData implements Data<Student> {
@Override
public void insert(Student student) {}
@Override
public Student remove() {}
@Override
public void update(Student student) {}
@Override
public Student query() {}
}
泛型通配符
概述:
通配符:?
?可以在“使用泛型”的时候代表一切类型。
ETKV是在定义泛型的时候使用的。
使用示例:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
//1.有一个汽车类,是所有汽车的父类
public class Car{}
//2.有两种汽车:奔驰、宝马
public class BaoMa extends Car{}
public class BenChi extends Car{}
//3.测试类
public class Test {
//4.定义了一个汽车赛跑的方法,使用了通配符 ?
//使该方法中的list集合可以接收任意类型的数据。
//若不使用通配符?,比如换成ArrayList<BaoMa> carList,那么这个方法就只能接收真实数据
//类型为BaoMa的集合,放入数据类型为BenChi的集合就会报错。
public static void run(ArrayList<?> carList){} public static void main(String[] args) {
ArrayList<BenChi> benChis = new ArrayList<>();
benChis.add(new BenChi());
benChis.add(new BenChi());
ArrayList<BaoMa> baoMas = new ArrayList<>();
baoMas.add(new BaoMa());
baoMas.add(new BaoMa());
run(benChis); //放入奔驰车
run(baoMas); //放入宝马车 }
}
注意:虽然BMW和BENZ都继承了Car但是ArrayList和ArrayList与ArrayList没有关系的!!可以理解为ArrayList-ENZ 类型和 ArrayList-Car类型,两者没有关系。
泛型的上下限:
? extends Car:?必须是Car或者其子类;泛型上限
? super Car:?必须是Car或者其父类 泛型下限
在上述赛车的案例中,由于使用了通配符?,就产生了问题。在赛跑的方法run()中传入存放类型为Dog的集合也可以,就形成了狗和汽车赛跑的局面,这是不符合逻辑的,所以这时候就需要使用泛型的上下限。
如何解决?
//使用泛型上限,?必须为Car的子类。此时传入非Car类型子类的类将报错。
public static void run(ArrayList<? extends Car> carList){}
Collections工具类
8.2.5 Set系列集合
Set集合的特点
无序:存取顺序不一致
不重复:可以去除重复
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合的功能上基本上与Collection的API一致。没有太多扩展的API
实现类特点:
HashSet:无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引。
TreeSet:排序、不重复、无索引。
HashSet集合无序的底层原理
概述:
HashSet集合底层采取哈希表存储的数据。
哈希表是一种对于增删改查数据性能都较好的结构。
什么是哈希表?
哈希表的组成:
IDK8之前的,底层使用数组+链表组成
JDK8开始后,底层采用数组+链表+红黑树组成。
哈希值:
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
如何获取对象的哈希值:Object类有一个 方法 public int hashCode() 返回对象的哈希值
哈希值的特点:同一个对象多次调用hashCode()方法返回的哈希值是相同的;默认情况下,不同对象的哈希值是不同的。
8.2.6 Map系列集合
8.2.7
8.3 Math、System、BigDecimal类
Math类
作用:包含执行基本数字运算的方法,Math类没有提供公开的构造器。
常用方法:
public static int abs(int a) :获取参数绝对值
public static double ceil(double a):向上取整
public static double floor(double a):向下取整
public static int round(float a):四舍五入
public static int max(int a,int b):获取两个int值中的较大值
public static double pow(double a,double b):返回a的b次幂的值
public static double random():返回值为double的随机值,范围[0.0,1.0),包前不包后。
System类
作用:System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化。
常用方法:
public static void exit(int status):终止当前运行的Java 虚拟机,非
零表示异常终止public static long currentTimeMillis():返回当前系统的时间毫秒值形式。返回1970-1-1 00:00:00 走到此刻的总的毫秒值。
public static void arraycopy(数据源数组,起始索引,目的地数组,起始
索引,拷贝个数):数组拷贝
BigDecimal类
作用:用于解决浮点型运算精度失真的问题
使用步骤:创建BigDecimal对象,封装浮点型数据,再使用BigDecimal对象的方法来进行加减乘除操作,就不会产生浮点型运算精度失真的问题。
注意事项:
【强制】禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。因为BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
BigDecimal类一定要进行精度运算,否则会发生异常。比如10.0 / 3.0 的运算,由于这个运算会得到一个无限小数,所以使用divide()方法进行除法运算时会发生异常。这时需要使用divide方法的重载方法,指定精确位数以及取整的模式来解决。
常用方法:
public static BigDecimal valueOf(double val):包装浮点数成为BigDecimal对象。
public BigDecimal add(BigDecimal b):加法
public BigDecimal subtract(BigDecimal b):减法
public BigDecimal multiply(BigDecimal b):乘法
public BigDecimal divide(BigDecimal b):除法
public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模
模式):除法
BigDecimal使用示例:
public static void main(String[] args) {
//double类型数据 0.1 和 0.2 在进行运算时会发生失真
//1.使用BigDecimal.valueOf(double b);方法创建对象
BigDecimal bigDecimal0 = BigDecimal.valueOf(0.1);
BigDecimal bigDecimal1 = BigDecimal.valueOf(0.2);
BigDecimal add = bigDecimal0.add(bigDecimal1); //加法
BigDecimal sub = bigDecimal0.subtract(bigDecimal1); //减法
BigDecimal mul = bigDecimal0.multiply(bigDecimal1); //减法
//除法:精确到第二位小数,向上取整。
BigDecimal divide = bigDecimal0.divide(bigDecimal1,2, RoundingMode.FLOOR);
}
8.4 时间和日期
Date类
概述:Date类的对象在Java中代表的是当前所在系统的此刻日期时间。
Date类构造器
Date():无参构造
Date(long b):传入毫秒值,会自将毫秒值转换为日期。
Date date = new Date();
System.out.println(date); //Tue Feb 28 15:21:36 CST 2023
记录时间的两种方式:1.使用Date对象记录 2.使用时间毫秒值记录
常用方法:
double getTime():将当前date对象代表的日期转换为毫秒值。
void setTime(long b):将当前date对象的日期转换为毫秒值b代表的日期。
SimpleDateFormat 类
作用:
可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式。
也可以把字符串的时间形式解析成日期对象。
构造器:
public SimpleDateFormat():构造一个SimnpleDateFormat,使用默认格式
构造一个Simpublic SimpleDateFormat(String pattern):npleDateFormat,使用指定的格式
时间格式的对应写法:
(此外还有 EEE 代表周几,a 代表依据时间给出上午或下午)
常用方法
public final String format(Date date):将日期对象格式化成日期/时间字符串
public final String format(Object time):将时间豪毫秒值式化成日期/时间字符串
public Date parse(String source): 从给定字符串的开始解析文本以生成日期对象
格式化时间演示:
public static void main(String[] args) {
//创建一个SimpleDateFormat对象,指定格式为:年月日 时分秒 周几 上|下午
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a"); //1.格式化日期对象所代表的日期:
Date date = new Date();
String format = dateFormat.format(date);
System.out.println(format); //输出:2023年02月28日 15:45:04 星期二 下午 //2.格式化毫秒值代表的日期。
String format1 = dateFormat.format(System.currentTimeMillis());
System.out.println(format1); //输出:2023年02月28日 15:45:04 星期二 下午
}
解析时间字符串演示:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日HH点mm分ss秒");
//解析字符串得到Date对象。
Date parse = dateFormat.parse("2021年08月06日11点11分11秒");
System.out.println(parse); //输出:Fri Aug 06 11:11:11 CST 2021
Calendar类
概述:Calendar代表了系统此刻日期对应的日历对象。Calendar是一个抽象类,不能直接创建对象。(作为一个日历,它肯定拥有比日期类更多的对于时间和日期操作的功能)
Calendar是一个抽象类,那么如何创建其对象?使用public static Calendar getInstance()方法获取一个实例。
常用方法:可以实现对日期的操作
public int get(int field):取日期中的某个字段信息。
public void set(int field,int value):修改日历的某个字段信息。
public void add(int field,int amount):为某个字段增加/减少指定的值
public final Date getTime():拿到此刻日期对象。
public long getTimeInMillis():拿到此刻时间毫秒值
特点:所有的操作都在一个日历对象中进行,不产生新对象。
使用演示:
//1、拿到系统此刻日历对象
Calendar cal =Calendar.getInstance();
System.out.println(cal); //包含了日历中的所有信息。 // 2、获取日历的信息:public int get(int field);取日期中的某个字段信息。
int year = cal.get(Calendar.YEAR); //获取年份
System.out.println(year);
int mm = cal.get(Calendar.MONTH) + 1;
System.out.println(mm);
int days = cal.get(Calendar.DAY_OF_YEAR);
Svstem.out.println(days); //3、public void set(int field int value): 修改日历的某个字段信息。
cal.set(Calendar.HOUR ,12);
System.out.println(cal); // 4.public void add(int field,int amount):为某个字段增加/减少指定的值
// 请问64天后是什么时间
cal.add(Calendar.DAY_OF_YEAR ,amount:64):
cal.add(Calendar.MINUTE,amount:59); // 5.public final date getTime(): 拿到此刻日期对象。
Date d =cal.qetTime();
System.out.println(d); // 6.public long getTimeInMillis():拿到此刻时间毫秒值
long time = cal.getTimeInMillis();
System.out.println(time);
JDK8开始新增的日期类
从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有:
新增的API严格区分了时刻、本地日期、本地时间,并且,对日期和时间进行运算更加方便。
其次,新API的类型几乎全部是不变类型(和String的使用类似),可以放心使用不必担心被修改。也就是说每次对于日期或时间对象的操作都会产生一个新对象,不改变原来的对象。
LocalDate类、LocalTime类、LocalDateTime 类
概述:他们 分别表示日期,时间,日期时间对象,他们的类的实例是不可变的对象。他们三者构建对象和API都是通用的。三个类对象甚至可以通过特定的方法实现互相转换
构建对象的方式:
常用方法:有很多,获取月份年份周几、修改时间、比较时间、解析时间等等很多很多
使用示例:最重要的是注意它和calendar类的不同。它每次操作都会产生新对象,不修改源对象。
// 1、获取本地日期对象。
LocalDate nowDate =LocalDate.now();
System.out.println("今天的日期:"+nowDate);//今天的日期:
int year =nowDate.getYear();
System.out.println("year: " + year);
int month =nowDate.getMonthValue();
System.out.println("month: " + month); //当年的第几天
int day0fYear =nowDate.getDay0fYear(); System.out.println(nowDate.getMonth()); //AUGUST
System.out.println(nowDate.getMonth().getValue()); //8
Instant类
作用:JDK8获取时间戳特别简单,且功能更丰富。Instant类由一个静态的工厂方法now()可以返回当前时间戳。
时间戳是包含日期和时间的,与iava.util.Date很类似,事实上Instant就是类似IDK8 以前的Date。
Instant和Date这两个类可以进行转换。
使用示例:
// 1、得到一个Instant时间戳对象
Instant instant =Instant.now();
//输出的时间跟系统时间相差8小时,因为世界标准时间跟东八区北京时间差8小时
System.out.println(instant);
// 2、系统此刻的时间戳怎么办?
Instant instant1 =Instant.now();
//ZoneId类用于指定时区
System.out.println(instant1.atZone(ZoneId.systemDefault()));
// 3、如何去返回Date对象
Date date =Date.from(instant);
System.out.println(date);
// 4、date对象转换为 instant对象。
Instant i2 =date.toInstant();
DateTimeFormatter类
概述:在JDK8中,引入了一个全新的日期与时间格式器DateTimeFormatter。正反都能调用format方法。它是一个线程安全的类。
常用方法:
static DateTimeFormatter ofPattern(String format):DateTimeFormatter类不通过构造器创建对象,而通过此方法直接获取一个对象。
String format(LocalDateTime dateTime):可格式化时间日期对象为字符串
LocalDateTime parse(String dateTime):解析字符串为时间日期对象。
使用示例:
// 本地此刻 日期时间 对象
LocalDateTime ldt =LocalDateTime.now();
System.out.println(ldt);
// 解析/格式化器
DateTimeFormatterdtf=DateTimeFormatter.ofPattern("vvvv-MM-dd HH:mm:ss EEE a"): // 正向格式化
System.out.println(dtf.format(ldt));
// 逆向格式化
System.out.println(ldt.format(dtf)); // 解析字符串时间
DateTimeFormatterdtf1=DateTimeFormatter.ofPattern("vvvy-MM-dd HH:mm:ss"):
//解析当前字符串时间成为本地日期时间对象
LocalDateTimeldt1=LocalDateTime.parse( text:"2019-11-11 11:11:11",dtf1);
System.out.println(ldt1);
System.out.println(ldt1.getDay0fYear());
Period类
概述:在Java8中,我们可以使用java.time.Period类来计算日期间隔差异,Period对象就代表了两个时间之间的间隔。主要是使用Period 类方法 getYears(),getMonths()和 getDays() 来计算,只能精确到年月日。用于 LocalDate 之间的比较。
创建对象:static Period between(LocalDate date1,LocalDate date2),会得到一个代表date2 - date1 日期间隔的对象。
使用示例:
// 当前本地 年月日
LocalDate today =LocalDate.now();
System.out.println(today);//
// 生日的 年月日
LocalDate birthDate =LocalDate.of(year:1998,month:10,dayOfMonth:13)
System.out.println(birthDate);
Periodperiod =Period.between(birthDate,today);//第二个参数减第一个参数
System.out.println(period.getYears());//相差年数
System.out.println(period.getMonths();//相差月数
System.out.println(period.getDays());//相差天数
Duration类:
概述:在Java8中,我们可以使用java.time.Duration类来计算时间间隔差异,Duration类对象就代表了两个时间之间的间隔。它提供了使用基于时间的值测量时间量的方法。用于 LocalDateTime 之间的比较。也可用于 Instant 之间的比较。
创建对象:static Duration between(LocalDateTime time1,LocalDateTime time2),会得到一个代表time2- time1时间间隔的对象。
使用示例:
// 本地日期时间对象。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 出生的日期时间对象
LocaldateTime birthDate =LocalDateTime.of(year:2021, month: 0
,dayOfMonth: 14, hour: 20, minute: 0o, second: 00);
System.out.println(birthDate); //获取对象。
Duration duration =Duration.between(birthDate,today);//第二个参数减第一个
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHoursO); //两个时间差的小时数
System.out.println(duration.toMinutesO));//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数
ChronoUnit类
概述:java.time.temporal.ChronoUnit类可用于在单个时间单位内测量一段时间,这个工具类是最全的了,可以用于比较所有的时间单位
使用示例:
//本地日期时间对象:此刻的
LocalDateTime today=LocalDateTime.now();
System.out.println(today);
// 生日时间
LocalDateTime birthDate=LocalDateTime.of( year: 1990, month: 10,dayOfMonth: 1,hour: 10, minute: 50, second: 59); //比较示例:
System.out.println(birthDate);
System.out.println("相差的年数:"+ChronoUnit.YEARS.between(birthDate,today));
System.out.println("相差的月数:"+ChronoUnit.MONTHS.between(birthDate,today));
System.out.println("相差的周数:"+ChronoUnit.WEEKS.between(birthDatetoday));
System.out.println("相差的天数:"+ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:"+ChronoUnit.HoURS.between(birthDate,today));
System.out.println"相差的分数:"+ChronoUnit.MINUTES.between(birthDate,today)):
System.out.println("相差的秒数:"+ChronoUnit.SEcONDS.between(birthDate,today));
System.out.println("相差的毫秒数:"+ChronoUnit.MILLIS.between(birthDate, today)):
System.out.println("相差的微秒数:"+ChronoUnit.MIcros.between(birthDate,today));
System.out.println("相差的纳秒数:"+ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:"+ChronoUnit.HALF DAYS.between(birthDate, today))
System.out.println("相差的十年数:"+ChronoUnit.DEcADEs.between(birthDate. today)):
System.out.println("相差的世纪(百年)数:"+ChronoUnit.CENTURIES.between(birthDate today));
System.out.println("相差的千年数:"+ChronoUnit.MILLENNIA.between(birthDate,today));
System.out.println("相差的纪元数:"+ChronoUnit.ERAS.between(birthDate, today)):
8.5 包装类
什么是包装类?其实就是8种基本数据类型对应的引用类型。
为什么提供包装类?
lava为了实现一切皆对象,为8种基本类型提供了对应的引用类型。
后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型。
自动装箱和拆箱
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量。
自动拆箱:包装类型的变量可以直接赋值给基本数据类型的变量。
包装类的特有功能
包装类的变量的默认值可以是null,容错率更高。
可以把基本类型的数据转换成字符串类型(用处不大)。也就是调用toString()方法得到字符串结果。
可以把字符串类型的数值转换成真实的数据类型(真的很有用,用于解析字符串)。比如:Integer.parselnt(“字符串类型的整数”)、Double.parseDouble(“字符串类型的小数”)。或者xxxxx.valueOf(String num):
8.6 Arrays类、lambda表达式
- Arrays类
概述:数组操作工具类,专门用于操作数组元素的。
常用方法::
public static String toString(类型[] a):返回数据内容
public static void sort(类型[]a):对数组进行默认升序排序
public static void sort(类型[] a, Comparator<? super T> c):使用Comparator比较器对象自定义排序
public static int binarySearch(intll a, int key):二分搜索数组中的数据,存在返回索引,不存在返回-1。使用二分查找算法来查找key,效率较高。需要注意的是,数组需要先排序才能使用二分查找算法。
使用Comparator比较器对象自定义排序规则:
sort(类型[] a, Comparator<? super T> c):第一个参数必须是引用类型数组,才可以使用这个方法进行排序;第二个参数是一个比较器对象,一般使用匿名内部类对象
自定义排序规则:设置Comparator接口对应的比较器对象,来定制比较规则。
如果认为左边数据 大于 右边数据 返回正整数
如果认为左边数据 小于 右边数据 返回负整数
如果认为左边数据 等于 右边数据 返回0
使用演示:
Integer arr[] = {1,5,4,2,3,6};
//定义了降序排序的规则。
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -(o1-o2);
}
});
System.out.println(Arrays.toString(arr)); //[6, 5, 4, 3, 2, 1]
应用:可用于对某些自定义的较为复杂的引用类型的数组进行排序。
public class Man {
private String name;
private int age;
private int height;
public Man(String name, int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
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 int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return "Man{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
} //测试
public static void main(String[] args) { Man mans[] = {
new Man("小明",19,170),
new Man("小东",15,179),
new Man("小明",21,171)
};
//自定义排序规则:按年龄升序排序
Arrays.sort(mans, new Comparator<Man>() {
@Override
public int compare(Man o1, Man o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println(Arrays.toString(mans)); }
- Lambada表达式
作用:Lambda表达式是JDK 8开始后的一种新语法形式。可以简化匿名内部类的代码写法。
简化格式:
注意事项:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
什么是函数式接口?
首先必须是接口、其次接口中有且仅有一个抽象方法的形式
通常我们会在接口上加上一个@FunctionalInterface注解,标记该接口必须是满足函数式接口。
Lambada基本使用演示:
interface Animal{
void run();
} public static void main(String[] args) {
// Animal animal = new Animal() {
// @Override
// public void run() {
// System.out.println("跑啊跑~");
// }
// };
// animal.run();
//与上方代码等同。使用lambada表达式实现匿名内部类。
Animal animal = ()->{
System.out.println("跑啊跑~");
};
animal.run();
} }
lambada表达式的省略规则:
参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号
号不写,同时要省略分号!如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
8.7 Stream流
8.8 IO流
九、日志
狂神视频全部看了,黑马从方法开始看。
写个计算器
01.JavaSE学习的更多相关文章
- 20145219 《Java程序设计》第01周学习总结
20145219 <Java程序设计>第01周学习总结 教材学习内容总结 软件分类:系统软件(DOS.Windows.Linux等).应用软件(扫雷.QQ等) 人机交互方式:图形化界面.命 ...
- javaSE学习笔记(17)---锁
javaSE学习笔记(17)---锁 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率.本文旨在对锁相关源码(本文中的源码来自JDK 8).使用场景进行举例,为读 ...
- javaSE学习笔记(10)---List、Set
javaSE学习笔记(10)---List.Set 1.数据存储的数据结构 常见的数据结构 数据存储的常用结构有:栈.队列.数组.链表和红黑树. 1.栈 栈:stack,又称堆栈,它是运算受限的线性表 ...
- JavaSE学习笔记(8)---常用类
JavaSE学习笔记(8)---常用类 1.Object类 java.lang.Object类是Java语言中的根类,即所有类的父类.它中描述的所有方法子类都可以使用.在对象实例化的时候,最终找的父类 ...
- javaSE学习笔记(16)---网络编程
javaSE学习笔记(16)---网络编程 基本概念 如今,计算机已经成为人们学习.工作.生活必不可少的工具.我们利用计算机可以和亲朋好友网上聊天,也可以玩网游.发邮件等等,这些功能实现都离不开计算机 ...
- javaSE学习笔记(15) ---缓冲流、转换流、序列化流
javaSE学习笔记(15) ---缓冲流.转换流.序列化流 缓冲流 昨天复习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流.比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化 ...
- JavaSE学习笔记(14)---File类和IO流(字节流和字符流)
JavaSE学习笔记(14)---File类和IO流(字节流和字符流) File类 概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. 构造方 ...
- JavaSE学习笔记(13)---线程池、Lambda表达式
JavaSE学习笔记(13)---线程池.Lambda表达式 1.等待唤醒机制 线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同. 比如:线程A用来生成包子的,线程B用 ...
- JavaSE学习笔记(12)---线程
JavaSE学习笔记(12)---线程 多线程 并发与并行 并发:指两个或多个事件在同一个时间段内发生. 并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在 ...
- javaSE学习笔记(11)--- Map
javaSE学习笔记(11)--- Map 1.Map集合 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Jav ...
随机推荐
- 第四篇:前端之BOM与DOM
前端基础之BOM和DOM 前戏 到目前为止,我们已经学过了JavaScript的一些简单的语法.但是这些简单的语法,并没有和浏览器有任何交互. 也就是我们还不能制作一些我们经常看到的网页的一些交互 ...
- 获取VIP歌曲
""" 分析需求 1,确定目标网址 2,获取目标网址的所有数据 3,筛选我们想要的数据 4,下载歌曲保存 """ import os imp ...
- Netty-BIO、NIO、AIO、零拷贝-2
Java BIO 编程 一.I/O 模型 1.I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能 2.Java 共支持 3 种网络编程模型/IO 模式:BI ...
- misc之套娃编码解密
题目: 01100101 01100110 00100000 01100010 01100101 00100000 00111001 01100110 00100000 01100011 011001 ...
- 就聊聊不少小IT公司的技术总监
本文想告诉大家如下两个观点. 1 很多IT小公司的技术总监,论能力其实也就是相当于大公司的高级程序员. 2 程序员在职业发展过程中,绝对应该优先考虑进大厂或好公司.如果仅仅停留在小公司,由于小公司可能 ...
- S2-009 CVE-2011-3923
漏洞名称 CVE-2011-3923 S2-009 远程代码执行漏洞 利用条件 Struts 2.0.0 - Struts 2.3.1.1 漏洞原理 OGNL提供了广泛的表达式评估功能等功能.该漏洞允 ...
- (11)go-micro微服务雪花算法
目录 一 雪花算法介绍 二 雪花算法优缺点 三 雪花算法实现 四 最后 一 雪花算法介绍 雪花算法是推特开源的分布式ID生成算法,用于在不同的机器上生成唯一的ID的算法. 该算法生成一个64bit的数 ...
- Ubuntu 安装 Anaconda
下载 anaconda 官网 https://repo.anaconda.com/archive/ 国内镜像 https://mirrors.bfsu.edu.cn/anaconda/archive/ ...
- threeJs构建3D世界
threejs官网 https://threejs.org/docs/index.html#manual/zh/introduction/Installation (官网非常的详细) 导入安装 npm ...
- 什么是C语言
什么是C语言? C语言是一门计算机语言 计算机语言是什么呢? 人和计算机交流的语言,如C/C++.Java.python 计算机语言的发展? 二进制语言(硬件-电-正电1/负电0 1010100101 ...