String 字符串
String 是一个类,属于数据类型中的引用数据类型,java的一切使用” “引起来的内容,都是这个类的实例,称为字符串对象
字符串在定义后,值不可以改变,是一个常量,实际是一个字符数组,所以可以使用String.length();(长度)
String name="tom";//不存在tom,所以创建一个tom,将其地址保存在name变量中
name="jerry";//jerry在字符串常量池中不存在,就创建一个,将其地址保存在name变量中
name中保存的从tom的地址变为了jerry的地址,但是两个字符串对象依然存在
String name2="tom";//tom在字符串常量池中已经存在了,直接使用,将其地址保存在name2中
其实以上三句话实际只创建了两个字符串对象
System.out.println(name==name2);//输出false,因为name=jerry啦
每次使用定义字符串时,如果该字符串已经存在,直接使用存在的字符串对象,
如果不存在就创建一个新的字符串对象;
String类使用注意事项
如果要频繁改动String类型变量的值,会创建很多字符串对象,效率很低,因此不要使用String类;
如果要频繁改动字符串时,使用StringBuilder或者StringBuffer类
如何创建字符串对象
1.使用“ ”赋值形式创建
String str="sbc";
String str="ab";//当这句话执行时,会判断ab是否存在,没有就创建,将其地址保存在str中;
String str2="ab";//执行这句话时,判断ab是否存在,已存在,将其地址保存在str2中,
String str3="a"+"b";//这句话执行时,+两端如果都是“”定义的字符串,
//拼接后在判断“ab”是否存在于字符串缓冲区中,已存在将其地址保存到str3中
//以上三句话,只有一个字符串对象创建,即“ab”,str1,str2,str3指向了同一个地址,
//所以用==比较都是true
sout(str==str2);//输出为true;
sout(str==str3);//输出为true
2.通过构造方法创建
常用构造方法 | 说明 |
String() | 创建一个空白字符串对象,即“ ” |
String(String str) | 创建一个指定字符串对象 |
String(char[] list) | 创建一个指定字符数组的字符串对象 |
String(byte[] bytes,String charsetName) | 按指定的编码格式创建一个指定字节数组的字符串对象 |
2.1使用构造方法String(String str)创建
//这句话执行流程:
//1、在字符串缓冲区中寻找ab,不存在,在缓冲区创建
//2、在堆中new String()创建对象将字符串缓冲区中的ab的字符串地址保存在new String()区域中
//3、将堆中new String()整个对象保存到栈中Str1变量中
String str1=new String("ab");
//这句话执行流程:
//1、在缓冲区中寻找ab,存在
//2、在堆中new String()创建对象将字符串缓冲区中的ab的字符串地址保存在new String()区域中
//3、将堆中new String()整个对象保存到栈中Str2变量中
String str2=new String("ab");
//以上两句话,在字符串缓冲区中有一个ab的字符串,在堆中有两个对象,
//str1和str2保存堆中不同的两个地址,所以输出weifalse
System.out.println(str1==str2);//false
3.使用+拼接“”和new出来的字符串对象创建
String str1="ab";//在字符串缓冲区中创建“ab";
//1、创建StringBuilder对象
//2、在字符串缓冲区中创建”a”;在字符串缓冲区中创建”b”;
//3、在堆中new String(),将“b”保存在其中
//4、调用StringBuilderd对象的append()方法,将“a”和new String("b")拼接
String str2="a"+new String("b");//一共会创建"a","b",new String(),
//new StringBuilder()四个对象
System.out.println(str1==str2);//false,比较两个不同地址
String类总结
在使用字符串时,字符串是对象,如果要比较两个值是否相同,不能使用==判断,因为==判断的是内存地址,所以在比较字符串是否相等时,要使用String类重写的equals方法进行判断
重写equals方法的原理:
判断是否为同一个字符串,再判断是否是字符串类型,再将两个字符串转换为字节数组,逐一比较字节数组中的内容,全部一致,返回为true;
字符串相关面试题
//题目一
String str1="ab";
String str2="ab";
String str3="a"+"b";
String str4=new String("ab");//使用字符串缓冲区中已经生成的ab,将其保存在new String()中
//创建"a"和"b",将”b“保存在new String(),将”a“和new String()保存在StringBuilder()对象中
String str5="a"+new String("b");
System.out.println(str1==str2);//true
System.out.println(str1==str3);//true
System.out.println(str1==str4);//false
System.out.println(str1==str5);//false
//题目二
String s1="abc";
String s2="a"+"b"+"c";
问:以上两句话执行后会创建几个对象?
答:一个,"abc"
//题目三
String str=new String("Hello");
问:这句话执行时会创建几个对象?
答:一个或者两个,
如果字符串缓冲区有"hello",只会创建new String(),这时只创建一个对象
如果字符串缓冲区没有"hello",会创建"hello"和new String(),这时会创建两个对象
//题目四
String s3=new String("wor");堆中new String(),和常量池”wor”
String s4=s3+"ld"; 堆中new StringBuilder()和常量池“ld”
以上两句话执行会创建几个对象?
四个
+号两都是“”赋值的字符串,拼接发生在编译阶段,将最终拼接的结果保存在字符串缓冲区中
其余情况都会创建StringBulider拼接字符串
字符串String类中的常用方法
方法名 | 返回值 | 作用 |
length() | int | 得到字符串长度 |
toUpperCase() | String | 转换为大写 |
toLowerCase() | String | 转换为小写 |
trim() | String | 去掉字符串的首尾全部空格 |
isEmpty() | boolean | 判断字符串长度是否为0 |
getBytes() | byte[] | 转换为字节数组 |
toCharArray() | char[] | 转换为字符数组 |
equalslgnoreCase(String str) | boolean | 忽略大小写比较字符串是否相同 |
equals(String str) | boolean | 判断两个字符串是否相同 |
charAt(int index) | char | 得到某个索引下标上的字符 |
indexOf(String str) | int | 得到某个字符串第一次出现的索引,不存在返回-1 |
lastIndexOf(String str) | int | 得到某个字符串最后一次出现的索引。不存在返回-1 |
contains(String str) | boolean | 判断是否包含指定的某个字符串 |
startsWith(String str) | boolean | 判断是否是以指定字符串开头 |
endsWith(String str) | boolean | 判断是否是以指定字符串结尾 |
concat(String str) | String | 将指定字符串拼接到原字符串末尾 |
substring(int index) | String | 从索引指定下标开始截取字符串至末尾 |
substring(int begin,int end) | String | 截取[begin,end)范围内的字符串 |
split(String regex) | String[] | 根据字符串或正则表达式切割原字符串 |
replace(String oldStr,String newStr) | String | 将原字符串中的oldStr替换为newStr |
String.valueOf(参数) | String | 将参数转换为字符串,参数可以是任何数据,通常用于原始类型转换为字符串 |
String.format(String 格式,Object...obj) | String | 根据指定格式转换参数,常用于将浮点数据保留指定小数位数,如String.format("%4.2f",2.345)表示将2.345保留2位小数,整体占4位,输出字符串格式,如果实际数字总为数大于4,原样输出,如果实际数字总位数小于4,会在最前补充空格 |
可变字符串StringBuilder、StringBuffer
String字符串对象是一个常量,在定义后,值不可以改变,如果使用String类的对象,对其频繁更新是,就会不停的创建新对象,不停引用给同一个变量;
例如:
如果要执行10000次循环重新赋值的过程,就会创建10000个字符串对象,每个对象的创建都需要时间和空间,循环时实际上不是更新字符串,而是创建字符串,效率很低,这就是需要使用可变字符串对象
StringBuilder
用于表示可变字符串的一个类,是非线程安全的,在单线程环境下使用,效率更高
StringBuffer
用于表示可变字符串的一个类,是线程安全的,在多线程环境下使用
StringBuilder和StringBuffer中的方法都一致,只不过StringBuffer中的方法使用了synchronized关键字修饰,表示是一个同步方法,在多线程环境下不会出现问题。
例如:PersonA类
public class PersonA implements Runnable{
private Object knife;
private Object fork;
private Object paper;
public PersonA(Object knife, Object fork,Object paper) {
this.knife = knife;
this.fork = fork;
this.paper=paper;
}
@Override
public void run() {
synchronized (paper){
synchronized(knife){
System.out.println(Thread.currentThread().getName()+"获得了刀,2秒后获得叉");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"已获得了叉和刀,可以开饭了");
}
}
}
}
PersonB类
public class PersonB implements Runnable {
private Object knife;
private Object fork;
private Object paper;
public PersonB(Object knife, Object fork,Object paper) {
this.knife = knife;
this.fork = fork;
this.paper=paper;
}
@Override
public void run() {
synchronized (paper){
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"获得了叉,2秒后获得刀");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"已经获得刀和叉,开饭了");
}
}
}
}
Main类
public static void main(String[] args) {
Object knife=new Object();
Object fork=new Object();
Object paper=new Object();
PersonA personA=new PersonA(paper,knife,fork);
PersonB personB=new PersonB(fork,knife,paper);
Thread thread1=new Thread(personA,"小张");
Thread thread2=new Thread(personB,"老王");
thread1.start();
thread2.start();
}
构造方法
StringBuilder、StringBuffer的构造方法都一样
常用构造方法 | 作用 |
StringBuilder() | 创建一个大小为16的字节数组,表示一个空白字符串 |
StringBuilder(int capacity) | 创建一个指定大小的字节数组,表示一个空白字符串 |
StringBuilder(String str) | 创建一个str的长度+16大小的字节数组,表示str这个字符串 |
常用方法
常用方法 | 作用 |
append(Object obj) | 将任意数据添加到原可变字符串末尾 |
delete(int start,int end) | 删除从[start,end)范围内的字符 |
deleteCharAt(int index) | 删除指定下标索引上的字符 |
insert(int index,Object obj) | 将obj添加到index指定下标上 |
replace(int start,int end,String str) | 将[start,end)范围内的字符替换成str |
reverse() | 反转字符串 |
注意
String类中所有的方法调用后,都会创建一个新的String对象,及原本的String字符串不会改变;
StringBuilder类中所有的方法都是在操作同一个字符串对象,每次调用方法,都会让原字符串发生改变;
StringBuilder类中没有重写equals的方法,所以判断两个可变字符串对象是否相同时,如果调用equals方法,实际调用的是Object中未重写的方法,即==判断,所以判断可变字符串是否相同时,需要将其转换为String对象再调用equals方法
可变字符串与String之间的转换
String转换为可变字符串
String str="hello";
//使用构造方法将String对象转换为StringBuilder对象
StringBuilder sb=new StringBuilder (str);
可变字符串转换为String(任意类型对象转换为String)
String.valueOf(Object obj)方法
StringBuilder sb=new StringBuilder ("hello");
//将任意类型对象转换为String对象
String str=String.valueOf(sb);
toString()方法
StringBuilder sb=new StringBuilder("hello");
//调用任意对象的toString()方法
String str=sb.toString();
拼接字符串
StringBulider sb=new StringBuilder("hello");
String str=sb+"";
可变字符串相关面试题
比较String,StringBuilder,StringBuffer的区别:
相同点:
这三个类都可以表示字符串,都提供了一些操作字符串的方法
这三个类中有相同的方法,如char(),indexOf()等
这三个类都是被final修饰的类,不能被继承
不同点:
String定义的字符串是一个常量,可变字符串定义的字符串是一个变量
String类中的方法调用后,不会改变原本字符串的值,可变字符串中的方法调用后,会改变原本字符串的值
StringBuilder是非线程安全的可变字符串,StringBuffer是线程安全的可变字符串,其中的方法被synchronized修饰
System类
这个类中包含了一些系统相关的信息和一些方法,其中的属性和方法都是静态的,该类不能创建对象,不是因为它是抽象类,而是因为它的构造方法是私有的。
常用方法和属性
常用方法和属性 | 说明 |
System.out | 获取标准输出流对象,用于打印信息 |
System.in | 获取标准输入流对象,获取输入的信息 |
System.err | 获取错误输出流对象,用于打印异常信息 |
System.exit(int statues) | 终止虚拟机运行,参数0表示正常终止 |
System.currentTimeMills() | 获取从1970/1/1 8:0:0至今经过了多少毫秒,返回值为long类型,通常称为时间戳 |
System.arraycopy(原数组,原数组的起始位置,目标数组,目标数组的起始位置,要复制的元素数量) | 将原数组中指定数量的元素复制到新数组中 |
Runtime类
Runtime类的对象,表示程序运行时对象(程序运行时环境对象)包含了程序运行环境相关的信息,常用语获取运行环境信息(如虚拟机信息)或执行某个命令
特点
这个类不是一个抽象类,但不能创建对象,因为它的构造方法是私有的,这个类提供了一个静态方法getRuntime(),通过该方法,可以获取一个Runtiem类的对象
这种方式可以保证该类只能创建一个对象,是java中的一种设计模式,:单例模式
public class Runtime{
//定义一个私有的静态成员,创建了一个当前类的对象
private static Runtime currentRuntime =new Runtime();
//将构造方法私有,无法在外创建对象
private Runtime();
//定义了一个公开的静态方法,用于获取创建的唯一的当前类的对象
public static Runtime getRuntime(){
return currentRuntime ;
}
}
Runtime方法的使用
使用Runtime类中的方法,先通过其静态方法获取他的对象
public class Test{
public static void main(String[]args)throws IOException,InterruptedException{
//使用Runtime类中的方法,先通过其静态方法获取他的对象
Runtime runtime=Runtime.getRuntime();
System.out.println("当前虚拟机的空闲内容"+runtime.totalMemory()/1024/1024+"MB");
System.out.println("当前虚拟机的实际最大内存"+runtime.freeMenory()/1024/1024+"MB");
System.out.println("当前虚拟机支持的最大内存"+runtime.maxMemory()/1024/1024+"MB");
//exec(String 指令名) 运行某个指令,返回运行的进程对象
//mspaint画图 calc计算器 notepad记事本
Process mspaint =runtime.exec("mspaint");//打开画图
Thread.sleep(3000);//休眠3秒
}
}
Date类
date日期 用于表示日期时间的类,位于java.util包下
构造方法
常用构造方法 | 说明 |
Date() | 创建当前时间对应的日期对象 |
Date(long l) | 创建指定瞬间对应的日期对象 |
Date(int year,int month,int date) | 根据年月日创建日期对象 |
常用方法
常用方法 | 作用 |
getTime() | 得到Date对应对象的毫秒数 |
after(Date when) | 判断参数是否在调用日期之后 |
before(Date when) | 判断参数是否在调用日期之前 |
SimpleDateFormat类
用于格式化日期的类
构造方法
常用构造方法 | 作用 |
SimpleDateFormat(String pattern) | 创建一个指定日期模板的格式化日期对象 |
日期模板
yyyy:年 MM:月 dd:日 hh:12小时制 HH:24小时制 mm:分 ss:秒 E:星期
如:yyyy/MM/dd/ HH:mm:ss E表示2023/03/09 14:05:16 周四
常用方法
常用方法 | 返回值 | 作用 |
format(Date) | String | 将Date对象按日期模板转换为字符串 |
parse(String str) | Date | 将满足日期模板的字符串转换为Date对象 |
格式
根据指定模板创建格式化日期对象
public class DateText {
public static void main(String[]args){
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss E");
//将满足日期模板的字符串转换为Date对象
String format=sdf.format(date);
System.out.println(format);
}
}
Calendar类
表示日历的类,包含了很多与日历相关的信息,Calendar是一个抽象类,无法创建对象,可以通过静态方法**getInstrance()**获取Calendar类的实例
//获取Calendar类的实例
Calendar c=Calendar.getInstrance();
日历字段
在Calendar类中,定义了很多被final static修饰的静态常量,称为日历字段,实际是一个数字,用于获取指定日历信息

Calendar常用方法
常用方法 | 作用 |
get(int field) | 根据日历字段获取相应的值 |
getMaximum(int field) | 获取指定日历字段的最大值,如日期最大为31 |
getActualIMaximum(int field) | 获取指定日历段的实际最大值,如11月的日期最大为30 |
getTime() | 将Calendar对象转换为Date对象 |
set(int year,int month,int date) | 同时设置日历的年月日 |
setTime(Date date) | 将Date对象作为参数设置日历的信息 |
set(int field,int value) | 将指定的日历段设置为指定值 |
方法调用时传值问题
第一种:参数是原始类型,方法内部对参数重新赋值,实参没有影响,main方法中是实际参数
public class Main{
public static void fun1(int i){
i=123;
System.out.println(i);
}
Main主函数
public static void main(String[]args){
int i=0;//实参
fun1(i);//输出为123,
System.out.println(i); //输出为0
}
}
第二种:参数是String,方法内部对参数重新赋值,实参没有影响
public static void fun2(String str){
str="new str";
System.out.println(str);
}
public static void main(String[]args){
String oldstr="old str";
fun2(oldstr);//输出"new str"
System.out.println(oldstr);输出"old str"
}
第三种:参数引用类型,方法内部在操作参数,实参受影响
public static void fun3(Person p){
p.setName("吴彦祖");
System.out.println(p.getName);
}
public static void main(String[]args){
Person p=new Person();
p.setName("小王");
fun3(p);//输出吴彦祖
System.out.println(p.getName());输出吴彦祖
}
第四种:参数是引用类型,方法内部对形参重新赋值后操作,实参没有影响
pblic static void fun4(Person p){
p=new Penson();
p.setName("胡歌");
System.out.println(p.getName());
}
public static void main(String[]args){
Person p=new Penson();
p.setName("小李");
fun4(p);//输出胡歌
System.out.println(p.getName());//输出小李
}
第五种:参数是引用类型,方法内部在操作参数,实参受影响
public static void fun5(int[] nums){
nums[0]=123;
System.out.println(dnum[0]);
}
public static void main(String[]args){
int[] nums={1,2,3,4};
fun5(nums);//输出123
System.out.println(nums[0]);//输入123
}
例题:
方法体
public static void fun5(char[] list,Person p){
list[0]='m';//方法体内部直接操作实际参数,会影响主函数中的实际参数
p=new Person();//方法内部创建了新对象给实际参数重新赋值,不会影响主函数的实际参数
p.setName("xxx");
}
main主函数
public static void main(String[]args){
Person p=new Person();
p.setName("qwe");//实参
char[] list={'a','b’,'c',''d};//实参
fun5(list,p);
System.out.println(p.getName());//输出qwe
System.out.println(list[0]);//输出m
}
包装类
java是纯面向对象语言,宗旨是将一切事物视为对象处理
包装类是原始类对应的类类型
特点
八个原始类型中,除了int和char,其余包装类都是将原始类型的首字母改为大写,int对应Integer,char对应Character
包装类都是被final修饰的,不能被继承
除了Character类,其余包装类都有两个过时的构造方法,参数为对应的原始类型或者字符串Character只有一个参数为char类型的构造方法,构造方法的目的都是将原始类型的数据转换为包装类的对象
除了Character类,其余包装类都有静态方法parse原始类型单词(String str),用于将字符串换为相应的原始类型
数值型的包装类parseXXX()方法,如果参数不是对应的数字,就会抛出NumberFormat异常
boolean的包装类Boolean的parseBoolean()方法,如果参数不是true这个单词,运行结果都是false
除了Boolean类,其余包装类都有MAX_VALUE和MIN_VALUE这两个静态属性,用于获取对应类支持的最大最小值
所有包装类都重写了toString(),用于将包装类对象转换为String对象
字符串与原始类型之间的转换
字符串转换为原始类型
使用原始类型对应的包装类,调用parseXXX(String str)方法
String num="123";
byte b==Byte.parseByte(num);//输出123
float l=Folat.parseFloat(num);//123.0
boolean b1=Boolean.parseBoolean(num);//false
原始类型转换为字符串
使用String类的String.valueOf(Object obj)
int num=123;
String str=String.valueOf(num);//输出字符串123
拼接空白字符串
int num=123;
String str=num+"";//输出字符串123
将原始类型转换为包装类后调用toString()
int num=123;
Integer integer=new Integer(num);
String str=integer.toString();//输出字符串123
装箱和拆箱
自动装箱缓冲区
i1和i2保存的数字在byte范围[-127,127]内,这个值会共享,只会有一个“100”的对象
Integer i1=100;
Integer i2=100;
System,out.println(i1==i2);//true,i1和i2引用一个地址
Integer i3=128;
Integer i4=128;
System,out.println(i1==i2);false
System,out.println(i1.equals(i2));//true,包装类重写了equals,会拆箱后判断
使用自动装箱给包装类对象赋值,值得范围在byte范围[-127,127]内,这个值会保存在缓冲区中,如果多个对象都使用这个值,共享一个数据,使用同一个地址,==判断结果为true;值得范围不在byte范围内,就会创建新的包装类对象,会有不同的地址,==判断结果为false;
引用类型对象比较相同时,不要使用==,包括包装类对象,比较相同时,使用包装类重写的equals方法
异常
当程序没有按照开发人员的意愿正常执行,中途出现的错误导致程序中断,称为异常
异常的产生
异常在程序中以对对象的形式存在,当代码执行过程中出现异常后,虚拟机会自动创建一个异常对象,如果没有对该异常对象进行处理,就会导致程序中断,不在执行后续内容,
异常的分类
Error错误
如果出现XXXError,如stackOverFlowError,栈溢出,无法通过额外的代码解决,只能修改源代码
Exception异常
RunTimeException运行时异常
如果一个异常类属于RuntiemExceotion异常类的子类,称这个异常为运行时异常,可以通过编译,运行时可以抛出异常对象
常见运行时异常 | 说明 | 出现的情景 |
NullPointException | 空指针异常 | 如用空对象null调用属性或者方法 |
IndexOutOfBoundsException | 索引越界异常 | 如当使用某个带有索引的对象超出范围 |
NumberFormatException | 数字格式异常 | 如调用包装类的parseXXX()方法,如果参数不能转换 |
InputMismatchException | 输入不匹配异常 | 如使用Scanner接受控制台输入时,如果输入的数据不是对应的类型 |
ClassCastException | 对象转换异常 | 如Person p=(Person )Dog dog |
ArithmeticException | 算术运算异常 | 如0当分母编译时异常 |
编译时异常
如果一个异常类不属于RunTimeException异常类的子类,称这个异常为编译时异常,无法通过编译,必须要处理异常后才能编译运行
常见编译时异常 | 说明 | 出现的情景 |
IOException | 输入输出流异常 | 使用流对象 |
FileNotFoundException | 文件未找到 | 方法的参数为文件对象时 |
SQLException | 数据库相关异常 | 操作数据库时出现 |
处理异常
通常所说的处理异常,指的是处理Exception类的子类异常
方式一:try-catch-finally语句
这种方式处理异常,无论会不会抛出异常,都能让程序正常执行
try{
//可能出错的代码
}catch(异常类 异常对象){
//如果try中的代码抛出异常,异常对象属于catch中的异常类型,就会执行这里的代码
}finally{
//无论程序是否出抛出异常,都要执行这里的代码
}
try-catch-finally使用时注意
如果代码会抛出多个异常,可以使用多个catch进行捕获,需要将子类异常放在最前面,父类异常放在最后面
try,catch,finally都不能单独使用,try必须配合catch或者finally一起使用
无论try中的代码是否会抛出异常,finally中的代码都会执行
执行try中的代码是,如果出现异常,就不再执行try中剩余代码。try中定义的内容,无法再try之外的地方使用
try中如果有return,不影响finally的执行,finally优先于return执行
方式二:throws关键字
该方法可以让编译时异常通过编译
在定义方法的时候,通过该关键字声明方法可能抛出的异常
用法:方法的参数部分后,添加throws异常类型1,异常类型2....
public class Test{
public void fun() throws InterruptedException{
thread.sleep(500);//该方法就会有一个声明:可能会抛出InterruptedException异常
sleep()方法在源码中声明了可能会抛出InterruptedException异常
InterruptedException异常不是RunTimeException的子类异常,必须要处理才能通过编译,要么使用try-catch,要么继续声明异常
}
}
throw和throws
throws表示用于声明方法可能出现的异常,使用时卸载方法的小括号之后
public void fun() throws InterruptedException{
thread.sleep(500);
}
throw用于手动抛出异常对象,使用时,卸载方法体中,“throw异常对象”。常用于满足某种条件时,强制中断程序
public void fun(){
throw;
}
自定义异常
如果需要在某种情况下中断程序,可以自定义一个异常类,再通过throw关键字手动抛出自定义异常类
自定义异常步骤
1、创建一个类,继承某个异常类
如果继承的是RunTimeException,表示自定义的异常类属于运行时异常,该异常对象可以不用处理
如果继承的是非RunTimeException,表示自定义的异常类属于编译时异常,该异常对象必须要处理
2、可选操作,定义带参构造方法,参数为String类型的异常信息,调用父类中的构造方法
/*
*自定义异常
只需要继承某个异常类即可
是否定义构造方法根据实际情况决定
*/
public class MyException extends NullpointerException{
//带参构造,参数为异常信息
public MyException(String msg){
super(msg);
}
//无参构造
public MyException(){
super();
}
}
数组和集合
数组的特点
数组中保存的元素都是有序的,可以通过索引快速访问
数组中保存的元素都是同一种类型
数组的长度在定义后,无法改变
数组无法获取其中保存的元素实际数量
集合的特点
能保存一组数据,元素可以有序或者无序(存入的顺序和读取的顺序不一致)
集合中保存的元素的数据类型可以不同
集合的容量可变
可以获取集合中保存的元素实际数量
集合框架


图上的所有实现类,都是非线程安全的,在多线程环境下使用以上任意集合,都会出现数据不准确的情况。
Collection接口
该接口中有两个核心子接口:List和Set
这两个接口都可以保存一组元素,List接口保存元素有序可重复的,Set接口保存元素无序不重复
Collection接口有一个父接口iterable,它不是一个集合,而是用于遍历集合的工具接口,包含forEach()和iterator()方法
常用方法 | 返回值 | 作用 |
add(Object obj) | boolean | 将元素添加到集合中 |
size() | int | 获取集合中的元素数量 |
isEmpty() | boolean | 判断集合是否为空 |
clear() | void | 清空集合 |
contains(Object obj) | boolean | 判断集合中是否包含指定元素 |
remove(Object obj) | boolean | 移除集合中的指定元素 |
toArray() | Object[] | 将集合转换为数组 |
stream() | Stream | 获取集合的流对象,用于遍历集合 |
iterator() | Iterator | 得到集合的迭代器对象,用于遍历集合 |
List接口(有序可重复)
有序集合,元素可以重复,允许保存null,可以通过索引获取对应的元素
List接口在继承Collention接口后,有拓展了一些操作元素的方法
拓展方法 | 返回值 | 作用 |
get(int index) | Object | 得到指定索引的元素 |
set(int index,Object obj) | Object | 使用obj替换index下标上的元素,返回被替换的元素 |
add(int index,Object obj) | void | 将obj添加到index上 |
remove(int index) | Object | 移除指定索引的元素,返回被移除的元素 |
indexOf(Object obj) | int | 得到obj第一次出现的索引 |
lastIndexOf(Objcet obj) | int | 得到obj最后一次出现的索引 |
subList(int form,int to) | List | 截取[from,to)区间内的元素,返回子集合 |
构造方法
构造方法 | 说明 |
ArrayList() | 创建一个Object类型的空数组,在调后续添加方法时,才会初始化数组大小 |
ArrayList(int initilaCapacity) | 创建一个指定容量的Object类型的数组,如果参数为负数,会抛出IllegalArgumentException异常 |
ArrayList(Collection c) | 根据指定集合创建Object类型数组 |
ArrayList和LinkedList的区别
这两个类都是List接口的实现类,保存的元素有序可重复,允许保存null
ArrayList采用数组实现,随机读取效率高,插入和删除效率低,是用于查询
LinkedList采用双向链表实现,插入和删除效率高,随机读取效率低,适用于频繁更新集合
遍历集合的方式
遍历List集合
普通for集合
for(int i=0;i<集合.size();i++){
元素 变量=集合.get(i);
}
增强for循环
for(数据类型 变量名:集合){
元素 变量=集合.get(i);
}
forEach()方法
集合.forEach(obj->{
元素 变量=集合.get(i);
});
迭代器
//Collection接口有一个父接口Iterable,其中有一个iterator方法用于获取迭代器对象遍历集合
//所有Collection的子实现类都能调用该方法
Iterator it = Collection集合.iterator();
//hasNext()判断是否还有下一个元素
while(it.hasNext()){
//next()方法读取该元素
元素 变量 = it.next();
}
遍历HashMap集合
泛型
一种规范,常用于限制集合中的元素类型,省去遍历集合时转换Object对象的过程
//默认可以保存任意类型的元素,即Object类型
List list=new ArrayList();
list.add(123);
list.add("hello");
//遍历时只能使用object类型获取
for(Object obj:list){
}
使用泛型
在定义集合时,在集合或者接口后写上<引用数据类型>
例如: 集合类或者接口<引用数据类型>集合遍历名=new 集合实现类();
//定义只能保存整数的集合,要使用整数的包装类类型
List <Integer> list=new ArrayList();
list.add(123);
//不能添加非整数
如list.add("hello");
Arrays数组工具类
包含了一些操作数组的静态方法
常用静态方法 | 说明 |
Arrays.Sort() | 对数组中的元素升序排序 |
Arrays.asList(T...obj) | 将可变参数转换为ArrayList集合 |
集合和数组之间的转化
数组转换为集合
//调用Arrays工具类的asList(1,2,6,22,11)或者asList(数组)
ArrayList<Integer>list=Arrays.asList(1,2,6,22,11);
集合转换为数组
ArrayList list=new ArrayList();
list.add("sdf");
list.add(123);
list.add(null);
//调用集合的toArray()方法
Object[] objs=list.toArray();
无论是数组转换集合还是集合转换数组,都可以进行遍历。
如果集合转换为数组,遍历集合,通过索引赋值。
如果数组转换为集合,遍历数组,通过add()添加元素
文件类File
java中的File类,表示本地硬盘中file或文件夹directory的一个类
构造方法
常用构造方法 | 说明 |
File(String pathName) | 根据文件的完整路径创建File对象 |
File(String parent,String child) | 根据文件的父目录的路径和自身的名称创建File对象 |
File(File parent,String child) | 根据文件的父目录对应的File对象和自身的名称创建File对象 |
常用方法
常用方法 | 作用 |
exists() | 判断文件是否存在 |
isFile() | 判断是否为文件 |
isDirectory() | 判断是否为文件夹 |
getName() | 获取文件名 |
getPath() | 获取文件相对路径 |
getAbsolutePath() | 获取文件绝对路径 |
length() | 获取文件字节大小 |
listFiles() | 得到文件夹中的第一层子文件对象的数组,返回file[] |
delete() | 删除文件或者空文件夹 |
mkdir() | 创建文件夹 |
斐波那契数列
public class Test2 {
public static void main(String[] args) {
//兔子问题
//有一对兔子,在第三个月开始,每个月都生一公一母两只小兔子
//假设所有兔子都不死亡,第10个月一共有多少只
//1月 2月 3月 4月 5月 6月 7月 8月 9月 10月
//1 1 2 3 5 8 13 21 34 55
//斐波那契数列
System.out.println(fun(3));
System.out.println(fun(10));
}
/*
* 递归调用
* */
public static int fun(int n) {
if (n > 2) {
return fun(n - 1) + fun(n - 2);
}
return 1;
}
}
递归遍历文件夹
public class Test3 {
public static void main(String[] args) {
//输出某个文件夹下的所有文件
File source = new File("D:\\GamePP Wonderful Moment");
showAllFile(source);
}
public static void showAllFile(File source){
//判断如果是文件夹
if(source.isDirectory()){
//展开第一层
for (File child : source.listFiles()) {
//child就是第一层的所有子文件,有可能还是文件夹,递归调用本方法
showAllFile(child);
}
}
//输出名称
System.out.println(source.getName());
}
}
流Stream
在Java中,流表示计算机硬盘与内存之间传输数据的通道
将内存中的数据存入到硬盘中,称为写write,也称为输出Output
将硬盘中的数据存入到内存中,称为读read,也称为输入Input
流的分类
字节输入流InputStream
FileInputStream、ObjectInputStream
字节输出流OutputStream
FileOutputStream、ObjectOutputStream
字符输入流Reader
FileReader、BufferedReader、InputStreamReader
字符输出流Writer
FileWriter、BufferedWriter、OutputStreamWriter
按方向分类
输入流:InputStream、Reader
读硬盘中的数据到程序中
输出流:OutputStream、Writer
将程序中的数据写到硬盘中
按数据传输类型分类
字节流:InputStream、OutputStream
读写非文本类型文件。如图片、多媒体文件
字符流:Reader、Writer
读写纯文本文件。如txt、md等
流的四个父类的特点
这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类对象
这四个类都有close()方法,用于关闭流对象,释放资源
输入流(InputStream和Reader)都有read()方法,用于读取数据,输出流(OutputStream和Writer)都有write()方法
输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中,在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写入到硬盘中
所有流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾,都是字符流,数据以字符传输
读取硬盘中的数据时,读取的文件必须存在;写入数据到硬盘中时,写入的文件可以不存在,但父
目录必须存在。
FileInputStream文件字节输入流(掌握)
构造方法

常用方法

FileOutputStream文件字节输出流(掌握)
构造方法

常用方法

FileReader文件字符输入流
构造方法

常用方法

BufferedReader缓冲字符输入流(掌握)
构造方法

常用方法

FileWriter文件字符输出流
构造方法

常用方法

BufferedWriter缓冲字符输出流(掌握)
必须在调用flush()或close()方法后才会写入。
构造方法

常用方法

序列化ObjectOutputStream对象字节输出流(掌握)
序列化:将对象转换为文件的过程
被序列化的对象,其类必须要实现Serializable接口
这个接口是一个特殊的接口,其中没有定义任何方法,只是给类加上标记,表示该类可以被序列化
构造方法

常用方法

反序列化ObjectInputStream对象字节输入流(掌握)
反序列化:将文件转换为对象的过程
构造方法

常用方法

进程和线程
进程Process
进程就是操作系统中正在执行的程序。
一个程序就是一个执行的进程实体对象。
每个运行中的进程,都有属于它独立的内存空间,各个进程之间互不影响。
线程Thread
线程是一个进程中的执行单元,一个进程中可以有多个线程。
多个线程之间可以访问同一个进程中的资源。
每个线程都有一块独立的栈空间,这些线程所在的栈空间位于同一个进程空间中。
多线程
如果一个进程中,同时执行着多个线程,就称为多线程。
多线程可以提高程序执行效率。如多个窗口卖票,就可以加快卖票的效率。
其实每个执行的Java程序,都是多线程执行。main方法所在的线程称为主线程,还有gc线程(守护线程)
在同时运行。
实现多线程
方式一:继承Thread类
方式二:实现Runnable接口
方式三:使用匿名内部类
多线程相关面试题
为什么说StringBuilder或ArrayList、HashMap是非线程安全的?
public class Test {
public static void main(String[] args) throws InterruptedException {
//使用StringBuilder时,很大概率最终数值不准确,非线程安全
//StringBuilder sb = new StringBuilder();
//使用StringBuffer时,没有问题,线程安全
StringBuffer sb = new StringBuffer();
//创建10个线程对象,创建后进入就绪状态
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//每个线程对象添加字符串
for (int j = 0; j < 10; j++) {
sb.append("hello");
}
}).start();
}
Thread.sleep(5000);
//如果没有任何问题,每个线程拼接50个字母,10个线程拼接500个,sb的长度为500
System.out.println(sb.length());
}
}
解决方法
方式一:让两个线程获取资源的顺序保持一致,如两个线程都先获取knife,再获取fork
方式二:在两个线程获取的资源A和B之前,再获取第三个资源C,对这个资源C使用synchronized进行同步这样在某个线程获取资源C后,继续执行后续的内容,知道执行完毕,其他线程才有机会开始执行。如在获取knife和fork之前,先获取paper对象
总结
多写多练 好记性不如烂笔头