目录
Java入门笔记
快捷键
Ctrl + D 复制当前行且粘贴
Ctrl + R 替换字符
Alt + Enter 错误提示
Ctrl + Shift + Y 翻译
Ctrl + ALT + L 整理代码
Ctrl + H 查看层次体系
Ctrl + P 可以查看所有重载方法的参数
.sout 打印.前面的东西
.var 为.前面的东西添加变量
.for 遍历.前面的东西
基础准备
环境准备
java -version 查看JDK版本
Java编译运行流程
源码(.java)经过编译器(javac.exe)编译后生成out文件夹中的字节码文件(.class)
java.exe启动虚拟机执行字节码文件
TODO
IDEA中可以在多行注释时写上TODO 表示待办的事情
自定义模板
Settings—Editor—Live Templates
可以设置快捷键模板
基础语法
变量原理
可以改变的向量存储就成为变量
标识符
标识数据的符号称为标识符
标识符命名规则
-
特殊符号只能使用美元$ 和下划线 _
-
数字不能出现在开头
-
标识符区分大小写
-
不能使用Java的保留字(static public …)
-
推荐使用驼峰命名法 userData myName
数据类型
byte : 8bit 1字节
short: 6bit
char : 16bit 正负
int : 32bit
整数型
java整数默认为int类型
byte A = 1; //1为Int类型
浮点型
java浮点数默认为双精度double类型
float A = 1.0; //这时候1.0为double类型
想要声明单精度float类型需要加上f / F
flaot A = 1.0f;
flaot A = 1.0F;
类型转换
小的类型能够直接转为大的类型,大的类型转为小的类型需要强转
比如:
float A = 1.0f;
double B = 2.0;
float C = B; //错误
float C = (float)B; //正确
引用数据类型
Q:为何int float 这些不称为引用数据类型而是基础数据类型
A:因为int float 这些基础数据类型存储在了向量存储的位置(红、黄色),他们没有箭头
面向对象
类和对象
- 类的首写字母是大写
- 数字特殊符号这些和标识符一样
Q:如何创建一个对象?
A:user变量在栈中创建,然后在元空间中找到这个类的结构,在堆中生成这个对象,最后栈中的user变量指向这个新生成的对象。
判断一个对象是不是某个类
instanceof
Object obj = new User();
if(obj instanceof User){
}
方法传参
可变参数
基本数据类型传参
传过去的只是形参,不会改变原来的
字符串数据类型传参
虽然字符串是引用数据类型,传过去的是指针,但是s=s+10会进行字符串拼接操作,堆中会生成一个拼接后的字符串。
引用数据类型传参
传过去的是对象地址,修改会改变对象的值
静态属性和方法
静态属性存在于元空间(也称方法区)内
直接用类名.静态方法();
静态属性同理
成员方法能够访问静态属性和静态方法(没static可以调用有static的)
静态方法不能够访问成员属性和成员方法(有static不可以调用没有static的)
类和对象都可以直接调用静态属性和静态方法
静态代码块
静态代码块会在类的信息被方法区加载后,被自动调用
静态代码块可以有多个,会按顺序执行
对象创建时也有类似的代码块
如下方法表示
{
}
包package
package mypkg;
规范来说一般都是小写
java.lang下的包自动导入(import)
导入Import
import需要写在包下面
导入多个包写法
import java.util.*;
构造方法
只是进行成员属性的初始化赋值,和对象创建无关
如果不写构造函数,JVM会自动生成一个无参的构造函数 权限为public
代码块在构造方法之前执行
继承
this & super
父类子类中都有同个属性时默认使用this关键字,使用super可以访问父类属性
子类调用父类
子类的构造方法会使用super() 去调用父类的构造方法,也就是说父类对象会在子类对象之前生成(这里不太准确,父类对象没有被生成new只会创建一个对象,红色部分是子类为从父类中继承过来的成员属性和成员方法开辟出的内存空间)
也就是说每创建一个子类对象都会创建一个父类对象(这里不太准确,父类对象没有被生成new只会创建一个对象,红色部分是子类为从父类中继承过来的成员属性和成员方法开辟出的内存空间)
如果父类自定义了带参数的构造函数,那么子类默认构造函数中自带的super() 就无法找到父类的构造函数
这时候就需要自己显示得使用带参数的super()
多态
多态时能不能使用子类中的成员方法,取决于这个方法在父类中存不存在,如果是子类独有的就无法调用(这里指的是能不能使用,而不是到底用哪个),如果父类子类都有的,会调用子类的
方法重载 overload
构造方法也可以重载
构造方法互相调用
构造方法间的互相调用需要使用到this关键字
基本数据类型自动扩增
byte作为参数传入,但是方法中并没有byte作为形参的方法时
会进行扩增 byte为一个字节 会优先选 short 这种两个字节的 而不是int这种4个字节的或者报错
但是byte类型无法转为char类型,因为byte类型有负数,char类型没有负数,所有byte在没有short时会转为int
引用数据类型自动扩增
基本数据类型找不到时,会扩增长度
引用数据类型找不到时,会寻找父类
比如这里并没有BBB类型的参数的test方法,于是JVM会寻找以BBB的父类AAA为参数的test方法
如下情况使用多态写成 AAA aaa = new BBB()
会直接报错,因为找不到AAA类型的会去找AAA的父类Object类,发现也没有,所以报错
方法重写 overrite
CCC ddd = new DDD(); 调用的是DDD中的i属性以及sum方法结果是40
因为多态时CCC决定的只是能否使用某个属性和方法,
如果这个方法父类没有子类有,就无法使用
如果这个方法父类和子类都有,就会调用子类中的方法
以下情况时结果会是20,而不是30
因为和方法不同,属性是哪里声明就在哪里使用,成员属性前默认有一个this.
以下结果为30
因为DDD中没有sum方法,所有会使用CCC中的sum方法,
CCC中的sum方法调用的getI方法调用的是DDD中的getI方法,因为方法具体使用是需要看具体对象的
而DDD中getI方法使用的i是DDD中的20 所以结果为30
访问权限
默认时,子包无法访问
Q:clone是Object类中的protected权限的方法,这里作为子类却无法调用,这是为啥呢?
A:注意到这里有两个子类都继承自Object类,而每个子类对象创建是都会有自己独立的父类内存空间,这里在子类A中去调用子类B的父类Object的clone方法就会出现没有权限的问题,而在子类B中调用子类B的父类Object的clone方法就没问题。
内部类和外部类
单例模式
保证只存在一个对象,这种多线程会有问题
实现方法:
- 构造函数权限设为private
- 内部创建一个private权限的类对象
- 对外暴露一个创建对象的函数,函数中判断是否已经有对象了
final
抽象类 abstract
抽象类和抽象方法前都有abstract
关键字
只要有抽象方法的就是抽象类,抽象类中可以有不抽象方法
抽象类是不能创建对象的,一个类继承抽象类后想要创建对象需要重写抽象类的抽象方法
抽象类和抽象方法都不能使用final修饰,因为分别无法被继承和被重写
接口 interface 实现 implements
接口和类是两个层面的东西
接口中的属性都是静态属性
,方法都是抽象方法
都是public权限
public class Java01_Variable {
public static void main(String[] args) {
Computer computer = new Computer();
Light light1 = new Light();
Light light2 = new Light();
computer.usb1 = light1;
computer.usb2 = light2;
computer.powerSupply();
}
}
//所有USB接口都要继承的接口
interface USBInterface{
}
//提供电源的接口
interface USBSupply extends USBInterface{
public void powerSupply();
}
//接收电源的接口
interface USBReceive extends USBInterface{
public void powerReceive();//接受电源
}
//电脑类,实现提供电源的接口,用于提供电源
class Computer implements USBSupply{
public USBReceive usb1; //电脑上接收电源的接口
public USBReceive usb2; //电脑上接收电源的接口
public void powerSupply() {
System.out.println("电脑提供能源");
usb1.powerReceive();
usb2.powerReceive();
}
}
//电灯类,实现了接收电源的接口,用于接受电源
class Light implements USBReceive{
public void powerReceive() {
System.out.println("电灯接受能源");
}
}
枚举类 enum
枚举是一个特殊的类,他的对象不能在外部创建,只能在内部创建
enum City{
BEIJING,SHANGHAI;//这俩其实就是枚举类的对象,此时使用默认的无参构造
}
public class Java01_Variable {
public static void main(String[] args) {
System.out.println(City.BEIJING.code);
System.out.println(City.SHANGHAI.name);
}
}
enum City{
//这俩其实就是枚举类的对象
BEIJING("北京" ,1000),SHANGHAI("上海",1001);
City(String name, int code) {
this.name = name;
this.code = code;
}
public String name;
public int code;
}
自定义枚举类
public class Java01_Variable {
public static void main(String[] args) {
System.out.println(MyCity.BEIJING.code);
System.out.println(MyCity.SHANGHAI.name);
}
}
class MyCity{
public String name;
public int code;
//使用private不然外部创建对象
private MyCity(String name, int code) {
this.name = name;
this.code = code;
}
public static final MyCity BEIJING = new MyCity("北京",1000);
public static final MyCity SHANGHAI = new MyCity("上海",1001);
}
匿名类
匿名类可以用来实现抽象类和接口
public class Java01_Variable {
public static void main(String[] args) {
Fly fly = new Fly() {
public void fly() {
System.out.println("飞");
}
};
fly.fly();
Run run = new Run() {
public void Run() {
System.out.println("跑");
}
};
run.Run();
}
}
interface Fly{
public void fly();
}
abstract class Run{
public abstract void Run();
}
Bean规范
- 抽象类,接口这些主要用于编写逻辑
- 类主要用于建立数据模型(Bean)
第二种类就称为Bean类,建立的对象就称为Bean对象
- 需要有无参公共的构造函数
- 需要有private的属性,并提供公共的setter getter
常用类和对象
Object类
java.lang.Object 是所有类的父类
toString()
把对象转为字符串
Object obj = new Person();
String str = obj.toString();
得到包名
.类名
@16进制表示的对象的内存地址
(tips:这里的内存地址指的是是hashcode,hashcode大部分情况是内存地址)
为了使得对象的内容变得更直观,我们也可以重写这个方法
Object obj = new Person();
String str = obj.toString();
class Person{
public String name = "zhangsan";
@Override
public String toString(){
return "Person : name = " + name;
}
}
hashCode()
获取对象的10进制表示的内存地址(其实并不是真正的内存地址)
Object obj = new Person();
int i = obj.hashCode(); //i为十进制表示的内存地址
equals()
判断两个对象是否相等,默认比较内存地址
Object obj = new Person();
boolean b = obj.equals(new Person()); //false
可以重写equals()
getClass()
获取对象的类型名称
Object obj = new Object();
Class<?> aClass = obj.getClass();
System.out.println(aClass.getSimpleName()); //获取对象的类名
System.out.println(aClass.getPackageName()); //获取对象的包名
数组
数组长度:数组.length
定义固定长度数组
int[] nums = new int[3]; //默认为0
User[] users = new User[4]; //默认为null
String[] strs = new String[5]; //默认为null 不是空字符串
for (int num: nums){
System.out.println(num);
}
定义的时候进行初始化
int[] nums = {1,2,3};
String[] strs = {"aaa","bbb","ccc"};
二维数组
int[][] nums = new int[3][4];
int[][] arr = new int[3][];// 此时一维数组还没有分配内存空间;arr[i]的地址为null;
for (int i = 0; i < arr.length; i++) {
// 给每个一维数组开辟空间,若没有这一步,一维数组的内存空间就是null
arr[i] = new int[i + 1];
}
java中的二维数组是并不是C++中那种行和列的数组,可以像下面这样每行个数不相等
java中是以这样排列实现的
冒泡排序
每轮找到最大值或最小值,放到最后面或者最前面
一轮需要进行多次交换数据
选择排序
每轮找到最大值或最小值,放到最后面或者最前面
一轮只需要1次交换数据
二分查找
在排好序的数组中,每次拿查看剩余部分中间的那个数与目标数比较,以此来每次去掉一半数
字符串
创建字符串
java.lang.String
//因为JVM进行了优化,内存中只会有一个“zhangsan”
String name1 = "zhangsan";
String name2 = "zhangsan";
String name3 = "zhangsan";
//无需使用这种方式创建字符串对象
String name = new String("zhangsan");
用char数组生成String
char[] cs = {'a','1','中'};
String s = new String(cs);
转义字符
\t \n \" \n \\
拼接 concat
“ + ” 拼接
数字也可以和字符串一起拼接
//下面两个String的hashcode是一样的,因为jvm优化了
String s1 = "a" + "b";
String s2 = "ab";
//会从左到右向加,数字之间做运算,数字字符串之间会将数字变为字符串
String s3 = 1 + "abc" + 2; //1abc2
String s3 = 1 + 2 + "abc"; //3abc
concat()
String s1 = "12";
s1.concat("abc"); //12abc
比较 equals compareTo
**相等 equals() **
String s1 = "a";
s1.equals("A"); //false
忽略大小写判断相等 equalsIgnoreCase()
String s1 = "a";
s1.equalsIgnoreCase("A"); //true
大小比较 compareTo()
正数左边比右边大,负数左边比右边小,0相等
String a = "a";
String b = "b";
a.compareTo(b); //a和b相比较
截取 subString
subString()
需要两个参数:
第一个开始位置(下标,包含)
第一个结束位置(下标,不包含)
String a = "Hello World";
b = a.subString(0,3); //Hel
想截下Hello也可以这样
String a = "Hello World";
b = a.subString(0,"Hello".length()); //Hello
b = a.subString(6,a.length()); //World
如果只提供一个参数,就是从指定位置截到最后
String a = "Hello World";
b = a.subString(6); //World
分解 split
split(字符) 按照指定字符分解字符串到数组中
String s = "Hello World ";
String[] strs = s.split(" ");
去除首尾空格 trim
trim() 只会去掉首位空格,不会去掉中间空格
String a = " Hello World ";
String b = a.trim();//Hello World
替换 replace
**replace(原来的字符串,修改的字符串) **
String a = "Hello World";
String b = a.replace("World","Java");//Hello Java
**replaceAll(规则,修改的字符串) ** 按照指定规则进行替换
String a = "Hello World zhangsan";
//这里规则使用了|表示这两种都进行替换
String b = a.replaceAll("World|zhangsan","Java");//Hello Java Java
大小写转换
**toLowerCase()**字符串全部变小写
**toUpperCase()**字符串全部变小写
Q:想把user变为User怎么办?
A:先截取u和ser出来,u变为大写后拼接
String a = “user";
String part1 = a.subString(0,1);
String part2 = a.subString(1);
part1 = part1.toUpperCase();
String result = part1 + part2; //User
查找 indexOf contains
toCharArray() 字符串转为char数组
charAt() 返回指定下标的字符
getBytes(字符集) 得到编码数字
String s = "Hello World";
char[] chars = s.toCharArray();
s.charAt(1);//e
byte[] bytes = s.getBytes("UTF-8");
indexOf() 查找字符串在另一个字符串第一次出现的下标位置
lastIndexOf() 查找字符串在另一个字符串最后一次出现的下标位置
String s = "World Hello World";
s.indexOf("World"); //0
s.lastIndexOf("World"); //12
contains() 是否包含指定的字符串
String s = "Hello World";
s.contains("World"); //true
startsWith() 是否以指定字符串开头
endsWith() 是否以指定字符串结尾
String s = "World Hello World";
s.startsWith("World"); //true
s.endsWith("World"); //true
isEmpty() 判断字符串是否为空 空格字符串不算空
String a = "";
a.isEmpty(); //true
String b = " ";
b.isEmpty(); //false
StringBuilder 效率高
用concat 或者 “+” 来拼接字符串效率很慢,因为每次拼接数字都需要创建字符串对象
我们可以使用StringBuilder来提高效率
StringBuilder s = new StringBuilder();
for (int i = 0; i < 100; i++) {
s.append(i);
}
System.out.println(s.toString()); //记得toString()
append(字符串) 拼接字符串
length() 获取长度
reverse() 反转
insert(下标,字符串) 插入字符串
包装类
让基本数据类型有了类的概念
int => Integer
char => Character
别的都是首字母大写
装箱
int i = 10;
Integer integer = new Integer(i); //不推荐使用了
Integer integer = Integer.valueOf(i); //推荐
Integer integer = i; //自动装箱,本质使用了valueOf
拆箱
int i = integer.intValue();
int i = integer; //自动拆箱,本质使用了intValue
日期类 Date
java.util.Date (注意不是sql下的Date)
System.currentTimeMillis(); //从1970以来的到现在的毫秒数(时间戳)
获取到日期后,还需要进行格式修改
使用 java.text.SimpleDateFormat
进行格式化
日期转字符串
format()
Date date = new Date();
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //设定格式
String format = simpleDateFormat.format(date); //传入日期
System.out.println(format);
字符串转日期
parse() 解析
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dataString = "2022-06-21";
Date parseData = simpleDateFormat.parse(dataString);
System.out.println(parseData);
日期类的一些操作
setTime() 设置时间戳
getTime() 获取时间戳
before(Date) 判断对象的的日期是否在括号内的之前
after(Date) 判断对象的的日期是否在括号内的之后
Date date1 = new Date();
date1.setTime(System.currentTimeMillis());
System.out.println(date1.getTime());
Thread.sleep(1000);
Date date2 = new Date();
System.out.println(date2.getTime());
System.out.println(date1.before(date2)); //true
System.out.println(date1.after(date2)); //false
日历类 Calendar
Calendar是一个抽象类无法创建对象,所有使用getInstance()获取实例
Calendar instance = Calendar.getInstance();
System.out.println(instance);
//java.util.GregorianCalendar[time=1670171217516,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=11,WEEK_OF_YEAR=50,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=339,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=26,SECOND=57,MILLISECOND=516,ZONE_OFFSET=28800000,DST_OFFSET=0]
获取年月日
System.out.println(instance.get(Calendar.YEAR));
//注意月份从0开始,所有会少一个月
System.out.println(instance.get(Calendar.MONTH));
System.out.println(instance.get(Calendar.DATE));
instance.add(Calendar.YEAR,-1); //可以修改
使用日期类Date给日历类时间
instance.setTime(new Date());
打印一个日历
这里需要注意的就是 Calendar.DAY_OF_WEEK 是从周日开始的(1开始)
System.out.println("一\t二\t三\t四\t五\t六\t日");
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 1); //设为当月第一天
int maxDay = calendar.getMaximum(Calendar.DAY_OF_MONTH); //获取当月总天数
for (int i = 0; i < maxDay; i++) {
int weekX = calendar.get(Calendar.DAY_OF_WEEK); //获取周几
int monthY = calendar.get(Calendar.DAY_OF_MONTH); //获取几号
if(i == 0){
//每月一号需要判断亲密要空多少制表符
if(weekX == Calendar.SUNDAY) {
//周日需要前面6个制表符然后换行
for (int j = 0; j < 6; j++) {
System.out.print("\t");
}
System.out.println(monthY);
}else{
//周一到六需要计算制表符
//weekX 从周日开始算 周日是1 周一是2 周二是3 周三是4
// 所以需要的制表符应该是weekX-2
for (int j = 0; j < weekX - 2; j++) {
System.out.print("\t");
}
System.out.print(monthY + "\t");
}
}else{
//不是一号的话
if(weekX == Calendar.SUNDAY){
//周日需要换行
System.out.println(monthY);
}else{
//周一到六需要制表符
System.out.print(monthY + "\t");
}
}
//增加一天
calendar.add(Calendar.DATE,1);
}
自定义工具类
判断字符串是否为空
这里的是否为空是自己定义的
- 字符串为null为空
- 字符串为空字符串为空
- 字符串中都是空格为空
public static boolean isEmpty(String str){
//2 3 两点进行了合并
if(str == null || "".equals(str.trim())){
return true;
}
return false;
}
生成随机字符串
纯随机字符串
public static String makeString(){
return UUID.randomUUID().toString();
}
生成指定随机字符串
要求字符串从给定范围选,长度限定
public static String makeString(String from, int len) {
if (len < 1) {
return "";
}else{
char[] chars = from.toCharArray();
StringBuilder str = new StringBuilder(); //StringBuilder效率高
for (int i = 0; i < len; i++) {
Random random = new Random();
//随机生成一个索引
int index = random.nextInt(chars.length);
//获取随机索引对应的字符
char c = chars[index];
str.append(c);
}
return str.toString();
}
}
转换字符串字符集
ISO8859-1 => str => UTF-8
public static String transform(String source, String encodeFrom, String encodeTo) throws UnsupportedEncodingException {
byte[] bytes = source.getBytes(encodeFrom);
return new String(bytes,encodeTo);
}
日期类和字符串互转
//字符串转日期
public static Date parseDate(String dateString, String format) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.parse(dateString);
}
//日期转字符串
public static String formatDate(Date date, String format) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(date);
}
比较
结论:基本数据类型用== 引用数据类型用equals()
int a = 10 float b = 10.0 这俩相等
字符串用双引号的会在字符串常量池生成
字符串new出来的不在字符串常量池
对象之间比较用== 和 equals 是一样的,因为equals方法中用的也是==
所以我们想要比较内容需要重写equals
一般重写时也会重写hashCode方法
包装类型
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2); //true
Integer i1 = 200;
Integer i2 = 200;
System.out.println(i1 == i2); //false
Q:这是为什么呢?
A:因为装箱使用的是valueOf 可以在看下面到这里如果数值不在缓存内,就会重新创建包装类对象
缓存的范围是 -128 to 127
@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
所以包装类最好使用equals()来比较大小
包装类的equals是进行拆箱后再比较的
异常
分为运行期异常和编译期异常
RuntimeException编译时不会给你提示
语法
int i = 0;
int j = 0;
try {
j = 10 / i;
}catch (ArithmeticException e){
e.getMessage(); // by zero
e.getCause(); // null
e.printStackTrace(); //打印堆栈信息见下图
}finally {
}
常见异常
运行时异常 通过编程能够解决的异常
- java.lang.ArithmeticException 算术异常 除数为0
- java.lang.NullPointerException 空指针异常
- java.lang.ArrayIndexOutOfBoundsException 数组越界
- java.lang.StringIndexOutOfBoundsException 字符串索引越界
- java.lang.NumberFormatException 格式化异常
- java.lang.ClassCastException 类型转换错误
-
java.lang.StringIndexOutOfBoundsException 例子
String s = "abc"; s.charAt(3); s.subString(4);
-
java.lang.NumberFormatException 格式化异常例子
String s = "abc"; Integer i = Integer.parseInt(s);
-
java.lang.ClassCastException 类型转换错误
Object a = new A(); B b = (B)a; class A{ } class B{ }
抛出异常
给调用者处理异常 throws 和 throw别看错了
- throws 抛出异常给上级
- throw 手动抛出异常
3.调用者处理异常
自定义异常
自定义异常需要继承自RuntimeException
public class Java01_Variable {
public static void main(String[] args) {
String account = "admin";
String password = "123";
//处理账号和密码异常
try {
login(account,password);
}catch (AccountException accountException){
System.out.println("账号不正确,需要重写修正");
}catch (PasswordException passwordException){
System.out.println("密码不正确,需要重写修正");
}catch (LoginException loginException){
System.out.println("登录相关的其他错误,需要重写修正");
}
}
//如果账号和密码有错误就抛出异常给调用者
public static void login(String account, String password) throws LoginException{
if(!"admin".equals(account)){
throw new AccountException("账号不正确");
}
if(!"admin".equals(password)){
throw new PasswordException("密码不正确");
}
System.out.println("登录成功");
}
}
//账号异常 继承自 登录异常
class AccountException extends LoginException{
public AccountException(String message) {
super(message);
}
}
//密码异常 继承自 登录异常
class PasswordException extends LoginException{
public PasswordException(String message) {
super(message);
}
}
//登录异常 继承自 运行时异常
class LoginException extends RuntimeException{
public LoginException(String message){
super(message);
}
}
进阶语法
集合
定义
集合是容纳数据的容器
根据存储数据的不同,Java的集合分为两大体系
- 单一数据体系: Collection接口定义的相关规则
- 键值对(Key,Value):Map接口定义了相关规则
常用接口和类
java.base.java.util
下有很多类和接口
Collection接口
常用子接口
-
List : 按照插入顺序保存数据,数据可以重复
具体实现类 ArrayList LinkList
-
Set : 集,无需保存,数据不能重复
具体实现类 HashSet
-
Queue : 队列
具体实现类 ArrayBlockingQueue
Ctrl + H 可以查看这些类和接口的层次结果
Map接口
具体实现类 HashMap Hashtable
Collection接口
单一数据体系
ArrayList类
底层由数组实现,数据可重复
创建ArrayList对象
一共三种创建方式
- 无参,底层为空数组
- 传一个int,用于设定底层数组长度
- 传一个Collection集合类型的值,用于把其他集合中的数据放置在当前集合中
ArrayList list = new ArrayList();
ArrayList list = new ArrayList(10);
增加数据 add
add()
添加数据时,如果ArrayList集合中没有任何数据,那么底层会创建长度为10的数组
ArrayList list = new ArrayList();
list.add("zhangsan");
添加数据时如果底层数组不够用了会自动扩容
创建一个长度更长的新数组,然后基本数据类型已过去,引用数据类型把指针移过去,然后弃用旧数组
指定下标插入数据,后面的会后移
list.add(1,"zhangsan");
addAll(otherList)
可以把其他List加入到末尾
ArrayList list = new ArrayList();
ArrayList otherList = new ArrayList();
list.addAll(otherList);
查看长度 size
list.size();
判空和清空
list.isEmpty(); //判空
list.cleat(); //清空
获取数据 get
获取指定下标的数据
list.get(1); //获取下标为1的数据,也就是第二个
遍历
这种遍历会按照顺序输出
ArrayList list = new ArrayList();
for(int i = 0; i < list.size(); i++){
sout(list.get(i));
}
如果不关心数据输出顺序的话,可以使用下面这种增强for循环
ArrayList list = new ArrayList();
for(Object obj : list){
sout(obj);
}
修改数据 set
修改指定下标的数据,会返回修改下标位置的旧值
ArrayList list = new ArrayList();
//把下标1处修改为zhangsan并返回旧值
Object objVal = list.set(1,"zhangsan");
删除数据 remove
删除指定下标的数据,会返回删除下标位置的旧值
ArrayList list = new ArrayList();
//把下标1处数据删除并返回该值
Object removeVal = list.remove(1);
removeAll(otherList) 删除另一个List中有的数据
ArrayList list = new ArrayList();
ArrayList otherList = new ArrayList();
list.addAll(otherList);
list.remove(otherList); //删除otherList
判断是否包含 contains
contains() 判断是否包含某个数据
ArrayList list = new ArrayList();
list.contains("zhangsan");
获取数据位置
indexOf() 获取数据在集合中第一次出现的的下标,不存在返回-1 , 有多个只会返回第一个
lastIndexOf()* 获取数据在集合中最后一次出现的的下标,不存在返回-1
list.indexOf("zhangsan");
list.lastIndexOf("zhangsan");
集合转数组 toArray
Object[] objs = list.toArray();
克隆集合 clone
clone() 是转为通用Object类型
Object clone = list.clone();
ArrayList list1 = (ArrayList)clone;
LinkedList类
带首尾指针的双向链表
创建LinkedList对象
LinkedList list = new LinkedList();
添加数据 add
add()
list.add("zhangsan");
add(index,element) 在指定下标添加数据
这种链表本来是没有下标的,但是我们把放入的数据从0开始当作有下标
list.add(1,"zhangsan");
addFirst() 在首部添加
addLast() 在尾部添加
addAll(otherList)
可以把其他List加入到末尾
LinkedList list = new LinkedList();
LinkedList otherList = new LinkedList();
list.addAll(otherList);
获取数据 get
可以获取首尾的数据
list.getFirst();
list.getLast();
获取指定下标的数据
list.get(1);
遍历
这种遍历会按照顺序输出
LinkedList list = new LinkedList();
for(int i = 0; i < list.size(); i++){
sout(list.get(i));
}
如果不关心数据输出顺序的话,可以使用下面这种增强for循环
LinkedList list = new LinkedList();
for(Object obj : list){
sout(obj);
}
修改数据 set
修改指定下标的数据,会返回修改下标位置的旧值
LinkedList list = new LinkedList();
//把下标1处修改为zhangsan并返回旧值
Object objVal = list.set(1,"zhangsan");
删除数据 remove
删除指定下标的数据,会返回删除下标位置的旧值
LinkedList list = new LinkedList();
//把下标1处数据删除并返回该值
Object removeVal = list.remove(1);
list.remove(“zhangsan“); //也可直接删除数据
remove() 无参表示删除第一个
removeFirst() 删除第一个
removeLast() 删除最后一个
其他常用操作
list.clear() //清除
list.contains("zhangsan") //是否包含
list.isEmpty(); //是否为空
list.size(); //长度
list.element(); //获取第一个数据
list.indexOf("zhangsan"); //获取第一次出现的下标
list.lastIndexOf("zhangsan"); //获取最后一次出现的下标
//首位置添加数据
list.push("zhangsan");
//首位置弹出数据
list.pop();
HashSet类
数据不可重复
,无序
,如果同个数据第二次出现,经过hash算法后和第一次相同,结果不会放入
创建HashSet
HashSet hashSet = new HashSet();
增加数据 add
add()
HashSet hashSet = new HashSet();
hashSet.add("a");
hashSet.add("b");
hashSet.add("c");
addAll() 把另一个集合放入这个集合
HashSet hashSet = new HashSet();
hashSet.add("a");
hashSet.add("b");
hashSet.add("c");
ArrayList arrayList = new ArrayList();
arrayList.add("zhangsan");
arrayList.add("lisi");
hashSet.addAll(arrayList);
/*
a
lisi
b
c
zhangsan
*/
删除数据 remove
hashSet.remove("b");
修改数据
HashSet无法直接修改,因为哈希算法的存在你无法把新的放到旧的那个位置
可以通过先删除再增加来实现
//b 改为 d
hashSet.remove("b");
hashSet.add("d");
遍历
for (Object obj :
hashSet) {
System.out.println(obj);
}
转为数组 toArray
Object[] objects = hashSet.toArray();
for (Object obj :
objects) {
System.out.println(obj);
}
其他常用方法
hashSet.isEmpty();
hashSet.size();
hashSet.contains("a"); //是否包含a
Object clone = hashSet.clone(); //克隆后强转
HashSet hashSet1 = (HashSet) clone;
重复数据
Q:如果有同个类的两个对象,他们的属性数据全都一样,放入HashSet后,他们是相同的吗
A:不相同,还是两个不同的数据
这里三个都可以成功存进去
如果想要让HashSet认为user1 和 user2 为相同的数据的话,我们需要重写HashCode 和 equals方法
public class Java01_Variable {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
User user1 = new User();
user1.id = 1001;
user1.name = "zhangsan";
User user2 = new User();
user2.id = 1001;
user2.name = "zhangsan";
User user3 = new User();
user3.id = 1002;
user3.name = "lisi";
hashSet.add(user1);
hashSet.add(user2);
hashSet.add(user3);
for (Object obj :
hashSet) {
System.out.println(obj);
}
//可以看到只有两个了
//User[1001,zhangsan]
//User[1002,lisi]
}
}
class User{
int id;
String name;
//重写hashCode,让其用id来进行hash
@Override
public int hashCode() {
return id;
}
//重写equals,判断两个对象是否相等
@Override
public boolean equals(Object obj) {
// 先判断类对不对,也是为了之后强转
if( obj instanceof User){
User otherUser = (User)obj;
if(otherUser.id == this.id){
if(otherUser.name.equals(this.name)){ //这里为了方便没判断空
return true;
}
}
}
return false;
}
@Override
public String toString() {
return "User["+id+","+name+"]";
}
}
如果id相同但是name不同的话,就发生了hash碰撞,会把第二个以链表的形式接在第一个数据的后面
Queue类
按照顺序,先进先出
ArrayBlockingQueue
类 存满了会堵塞
创建对象
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
存入数据
三种方式存入
queue.add("a"); //满了再存直接报错
queue.put("b"); //满了再存会一直等待
boolean b = queue.offer("c"); //返回是否存入成功
取出数据
queue.poll(); //取完了再取返回null
queue.take(); //取完了再取会一直等待
Map接口
键值对类型
HashMap类
存储无序,不能重复,根据key判断是否相同
与HashSet不同的是,HashMap有覆盖的概念
如果两个Key一样,Value不一样的键值对放入HashMap,后面放入的会覆盖掉前面的
HashMap hashMap = new HashMap();
hashMap.put("a","zhangsan");
hashMap.put("a","lisi"); //覆盖
System.out.println(hashMap.get("a")); //lisi
而如果Key不同,但是经过hash计算后结果相同的话,就会发生hash碰撞
这时候会继续往下链,但是如果长了以后相率非常慢,JDK使用红黑树进行优化
创建对象
HashMap hashMap = new HashMap();
添加修改数据 put
put() 如果key本来就已经有了就会覆盖(等于修改),并返回被覆盖的值
hashMap.put("a","zhangsan");
hashMap.put("b","lisi");
//如果key本来就已经有了就会覆盖,并返回被覆盖的值
Object oldValue = hashMap.put("a","123"); //zhangsan
putIfAbsent() 如果map中key不存在就放入,如果key以及存在就不放入
hashMap.putIfAbsent("b","456");
replace() 替换修改数据,如果key不存在返回null,存在返回被替换的值(真正的修改函数)
hashMap.replace("b","456");
获取数据 get
hashMap.get("a");
删除数据 remove
hashMap.remove("a"); //key对上就删
hashMap.remove("a","zhangsan"); //key和value都对上才删
获取所有Key
获取Map集合总所有的Key
HashMap hashMap = new HashMap();
hashMap.put("a","zhangsan");
hashMap.put("c","lisi");
hashMap.put("b","wangwu");
Set set = hashMap.keySet();
System.out.println(set);
//[a, b, c]
获取所有键值对
不加泛型时
HashMap hashMap = new HashMap();
hashMap.put("a","zhangsan");
hashMap.put("c","lisi");
hashMap.put("b","wangwu");
Set set = hashMap.entrySet();
for (Object o : set) {
System.out.println(o);
}
//a=zhangsan
//b=wangwu
//c=lisi
加泛型时
HashMap<String,String> hashMap = new HashMap();
hashMap.put("a","zhangsan");
hashMap.put("c","lisi");
hashMap.put("b","wangwu");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() +"="+ entry.getValue());
}
//a=zhangsan
//b=wangwu
//c=lisi
常用方法
hashMap.clear(); //清空
hashMap.containsKey("a"); //判断map集合中有无指定key
hashMap.containsValue("123"); //判断map集合中有无指定value
Hashtable类
注意t是小写
和HashSet的区别
- 继承的类不一样,Hashtable 继承自 Dictionary
- 底层容量不同,HashMap默认容量16 Hashtable默认容量11
- HashMap的K,V都可以为null Hashtable K,V都不能为null
- 底层做Hash运算不一样,HashMap采用Hash算法,Hashtable采hashcode方法
- 性能差别,HashMap性能较高,Hashtable较低因为考虑多线程冲突
基本操作
和HashMap基本一样
迭代器
为什么要用迭代器
下面的代码运行会报错,这是因为再遍历的中途把hashMap的中间一个键值对给删了
但是Set并不知道,所以还会往下遍历,导致出错
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("a",1);
hashMap.put("b",2);
hashMap.put("c",3);
Set<String> keys = hashMap.keySet();
for (String key : keys) {
if("b".equals(key)){ //如果这里b改为c不会报错,因为以及遍历到后面了前面无所谓
hashMap.remove("b");
}
System.out.println(hashMap.get(key));
}
Q:那有没有办法再修改map的时候,把map改变了通知set呢?
A:使用迭代器
迭代器使用
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("a",1);
hashMap.put("b",2);
hashMap.put("c",3);
Set<String> keys = hashMap.keySet();
Iterator<String> iterator = keys.iterator();
//hasNext() 用于判断有没有下一条数据
while (iterator.hasNext()){
String key = iterator.next();
if("b".equals(key)){
//使用迭代器来删除,会同时更新数据
iterator.remove(); //注意remove只能对当前数据删除,不能对别的数据删除
}
System.out.println(hashMap.get(key));
}
//1
//null
//3
泛型
约束集合的数据类型
为什么要使用泛型
从集合中取出的对象类型为Object
因为多态限制的原因,父类Object并不能使用子类的方法
如果此时想要使用子类的方法,就需要进行类型强转
但是此时问题又出现了,集合内部可能会发生变化,不能确定取出的对象的类型
这就可能发生类型转换错误
所以拿出来前还需要进行类型的判断 这种太麻烦了
如何使用泛型
原本集合内可以存各种类型的数据,使用泛型直接把集合限定为只能使用某一种类型
有时泛型也称为类型参数
此时不设定泛型,那么data的类型是什么都可以
public class Java01_Variable {
public static void main(String[] args) {
MyContainer myContainer = new MyContainer();
//此时不设定泛型,那么data的类型是什么都可以
myContainer.data = new Object();
}
}
//这里的C表示类
class MyContainer<C>{
public C data;
}
class User{
}
此时设定泛型,那么data的类型只能是User
public class Java01_Variable {
public static void main(String[] args) {
//此时设定泛型,那么data的类型只能是User
MyContainer<User> myContainer = new MyContainer();
myContainer.data = new User();
}
}
class MyContainer<C>{
public C data;
}
class User{
}
注意
Q:Object是User的父类,限定为Object的泛型,输入Object的子类User可以吗?
A:不行,泛型没有多态
public class Java01_Variable {
public static void main(String[] args) {
MyContainer<User> myContainer = new MyContainer();
test(myContainer);
}
public static void test(MyContainer<Object> myContainer){
System.out.println(myContainer); //报错!
}
}
class MyContainer<C>{
public C data;
}
class User{
}
比较器接口
可以用于排序
需要去实现Comparator接口,重写compare方法
package chapter02;
import java.util.ArrayList;
import java.util.Comparator;
public class Java01_Variable {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);
list.add(3);
list.add(2);
//sort 需要传入一个Comparator比较器
list.sort(new NumberComparator());
System.out.println(list);
}
}
//需要实现Comparator接口
class NumberComparator implements Comparator<Integer>{
//需要重写compare方法
@Override
public int compare(Integer o1, Integer o2) {
//如果第一个数大,返回正数,表示增序
return o1-o2;
//如果第二个数大,返回负数,表示降序
//return o2-o1;
//如果两个数一样大,返回0
//return 0;
}
}
工具类
静态方法
Arrays类
java.util.Arrays
toString() 方便打印出具体数据
int[] i = {1,2,3,4};
System.out.println(Arrays.toString(i));
System.out.println(i);
//[1, 2, 3, 4]
//[I@776ec8df
asList() 数组、数字变集合
int[] i = {1,2,3,4};
List<int[]> ints = Arrays.asList(i);
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
sort() 排序,默认升序
int[] i = {2,3,4,7,8,9,1};
Arrays.sort(i);
System.out.println(Arrays.toString(i));
binarySearch() 二分查找,注意这个返回的是排序
后的下标
int[] i = {7,8,9,1,4};
Arrays.sort(i); //排序
int index = Arrays.binarySearch(i,4);
System.out.println(index);
equals() 判断数组相不相等 数据和数据的顺序都相等才算相等
int[] a = {7,8,9,1,4};
int[] b = {7,8,9,1,4,5};
System.out.println(Arrays.equals(a, b)); //false
选取范围比较
int[] a = {7,8,9,1,4};
int[] b = {7,8,9,1,4,5};
Arrays.equals(a,0,3, b,0,3); //true
集合异常
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 0
List 的索引范围和数组不同,List的是0~实际数据的长度-1
java.util.NoSuchElementException
LinkedList list = new LinkedList();
list.getFirst(); //此时还没有第一个数据
java.util.ConcurrentmodificationException
因为修改了数据数量,只改值没事
ss Java01_Variable {
public static void main(String[] args) {
MyContainer myContainer = new MyContainer();
test(myContainer);
}
public static void test(MyContainer<Object> myContainer){
System.out.println(myContainer); //报错!
}
}
class MyContainer{
public C data;
}
class User{
}
<img src="https://i-blog.csdnimg.cn/blog_migrate/eec14d2416a0d24c0abf302b553e5211.png" alt="image-20221205204509035" style="zoom:50%;" />
#### 比较器接口
可以用于排序
需要去实现Comparator接口,重写compare方法
~~~java
package chapter02;
import java.util.ArrayList;
import java.util.Comparator;
public class Java01_Variable {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1);
list.add(3);
list.add(2);
//sort 需要传入一个Comparator比较器
list.sort(new NumberComparator());
System.out.println(list);
}
}
//需要实现Comparator接口
class NumberComparator implements Comparator<Integer>{
//需要重写compare方法
@Override
public int compare(Integer o1, Integer o2) {
//如果第一个数大,返回正数,表示增序
return o1-o2;
//如果第二个数大,返回负数,表示降序
//return o2-o1;
//如果两个数一样大,返回0
//return 0;
}
}
工具类
静态方法
Arrays类
java.util.Arrays
toString() 方便打印出具体数据
int[] i = {1,2,3,4};
System.out.println(Arrays.toString(i));
System.out.println(i);
//[1, 2, 3, 4]
//[I@776ec8df
asList() 数组、数字变集合
int[] i = {1,2,3,4};
List<int[]> ints = Arrays.asList(i);
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
sort() 排序,默认升序
int[] i = {2,3,4,7,8,9,1};
Arrays.sort(i);
System.out.println(Arrays.toString(i));
binarySearch() 二分查找,注意这个返回的是排序
后的下标
int[] i = {7,8,9,1,4};
Arrays.sort(i); //排序
int index = Arrays.binarySearch(i,4);
System.out.println(index);
equals() 判断数组相不相等 数据和数据的顺序都相等才算相等
int[] a = {7,8,9,1,4};
int[] b = {7,8,9,1,4,5};
System.out.println(Arrays.equals(a, b)); //false
选取范围比较
int[] a = {7,8,9,1,4};
int[] b = {7,8,9,1,4,5};
Arrays.equals(a,0,3, b,0,3); //true
集合异常
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 0
List 的索引范围和数组不同,List的是0~实际数据的长度-1
java.util.NoSuchElementException
LinkedList list = new LinkedList();
list.getFirst(); //此时还没有第一个数据
java.util.ConcurrentmodificationException
因为修改了数据数量,只改值没事
IO
数据流
数据 + 流转操作
Java中管道不止一个,各个管道各不相同
输入输出时都有阀门,可以控制是否开启
文件流
Java无法直接访问系统中的文件,需要使用文件对象(紫色部分)
File:文件类型(文件、文件夹),属于java.io
创建文件对象
创建对象后需要进行关联,关联的方法就是把路径写进去
File file = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt");
System.out.println(file); //D:\MyCode\IDEA\java-top-speed\data\word.txt
判断文件类型
文件类型(文件、文件夹)
file.isFile(); //是否为文件
file.isDirectory(); //是否为文件夹
判断文件是否存在存在
file.exists()
创建文件和文件夹
如果文件不存在,可以船舰文件
如果文件夹不存在,可以创建这个不存在的文件夹(多级)
file.createNewFile();
file.mkdirs();
文件常用方法
file.getName(); //文件名
file.length(); //文件内容长度
file.lastModified(); //最后修改时间 毫秒
file.getAbsolutePath(); //绝对路径
文件夹常用方法
file.getName(); //文件名
file.lastModified(); //最后修改时间 毫秒
file.getAbsolutePath(); //绝对路径
//打印文件夹下所有文件名称
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
//获得文件夹下所有文件对象
File[] files = file.listFiles();
for (File file1 : files) {
System.out.println(file1);
}
文件复制
需要用到文件输入流和文件输出流这两个管道对象,控制他们的阀门是否打开
如果只read() 和 write()一次,目标文件中只会有一个字符,所以需要进行循环
public static void main(String[] args) {
//源文件
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt");
//目标文件(自动生成)
File destFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt.copy");
//文件输入流(管道对象)
FileInputStream in = null;
//文件输出流(管道对象)
FileOutputStream out = null;
try {
in = new FileInputStream(srcFile);
out = new FileOutputStream(destFile);
//打开输入阀门,流转数据,每次只会读一个字符
//如果已经全部读完了,读到的结果会是-1,表示结束了
int data = in.read();
//打开输出阀门,流转数据,每次只会写一个字符
out.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭
if(in != null){
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
循环判断文件是否已经读完
public static void main(String[] args) {
//源文件
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt");
//目标文件(自动生成)
File destFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt.copy");
//文件输入流(管道对象)
FileInputStream in = null;
//文件输出流(管道对象)
FileOutputStream out = null;
try {
in = new FileInputStream(srcFile);
out = new FileOutputStream(destFile);
int data = -1;
//打开输入阀门,流转数据,读一个字符进行判断
//如果已经全部读完了,读到的结果会是-1,表示结束了
while( (data = in.read()) != -1 ){
//打开输出阀门,流转数据,每次只会写一个字符
out.write(data);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭
if(in != null){
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
缓冲流
每读写一个字符都需要分别打开关闭一次输入流管道阀门和输出流管道阀门
这样效率太低了,使用缓冲流能够一次开关阀门就读很多个字符
我们对输入管道和输出管道分别对接上一个缓冲区(Buffer)
中间加个水桶(cache)
- 打开输入缓冲管道阀门
- 把数据都先放到输入Buffer管道中
- 把输入Buffer的数据读取到cache中
- 把输入cache的数据写入输出Buffer中
- 关闭输入缓冲管道阀门
- 把数据传输到目标文件
public static void main(String[] args) {
//源文件
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt");
//目标文件(自动生成)
File destFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt.copy");
//文件输入流(管道对象)
FileInputStream in = null;
//文件输出流(管道对象)
FileOutputStream out = null;
//缓冲输入流(管道对象)
BufferedInputStream bufferIn = null;
//缓冲输出流(管道对象)
BufferedOutputStream bufferOut = null;
//缓冲区(水桶) 1024表示最大容量
byte[] cache = new byte[1024];
try {
in = new FileInputStream(srcFile);
out = new FileOutputStream(destFile);
//把缓冲流对接上文件流
bufferIn = new BufferedInputStream(in);
bufferOut = new BufferedOutputStream(out);
int data = -1;
//从输入缓冲中读到cache中 如果数据比cache的容量大就会分次,每次读cache最大容量
while( (data = bufferIn.read(cache)) != -1 ){
//cache中读到输出缓冲中 0表示从头开始写 data表示读了多少写入多少
bufferOut.write(cache,0, data);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭,注意这里关闭的时缓冲流的阀门
if(bufferIn != null){
try {
bufferIn.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(bufferOut != null){
try {
bufferOut.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
字符流
上面只是对数据进行复制,想要对读取的数据做处理的话需要用到字符流
字符在这里采用Unicode编码 一个中文占3个字节 一个英文占1个字节
对字符串进行操作使用StringBuilder
文件流以字节的方式读取数据,字符流以字符串的方式读取数据
public static void main(String[] args) {
//源文件
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt");
//目标文件(自动生成)
File destFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\word.txt.copy");
//字符输入流(管道对象)
BufferedReader reader = null;
//字符输出流(管道对象)
PrintWriter writer = null;
try {
//BufferedReader需要一个文件读对象
reader = new BufferedReader(new FileReader(srcFile));
writer = new PrintWriter(destFile);
StringBuilder stringBuilder = null;
String line = null;
//读到字符串为null说明读读完了
while( (line = reader.readLine()) != null ){
stringBuilder = new StringBuilder();
stringBuilder.append(line);
stringBuilder.append(" 附加内容");
//往输出管道中写字符串
writer.println(stringBuilder);
}
//刷写数据 把内部缓冲区的数据全部强制送到目的地
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭
if(reader != null){
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(writer != null){
writer.close();
}
}
}
序列化和反序列化
序列化:把对象存到文件中
反序列化:把文件中的数据变为对象
序列化
写入的类需要实现Serializable接口
public class Java01_Variable {
public static void main(String[] args) {
//数据文件对象
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\obj.dat");
//对象输出流(管道对象)
ObjectOutputStream objectOut = null;
FileOutputStream out = null;
try {
out = new FileOutputStream(srcFile);
//需要传入一个FileOutputStream对象
objectOut = new ObjectOutputStream(out);
//Java中只有实现Serializable接口,才能进行序列化
User user = new User();
//写入对象
objectOut.writeObject(user);
//刷写
objectOut.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭
if(objectOut != null) {
try {
objectOut.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
class User implements Serializable{
}
反序列化
public class Java01_Variable {
public static void main(String[] args) {
//数据文件对象
File srcFile = new File("D:\\MyCode\\IDEA\\java-top-speed\\data\\obj.dat");
//对象输入流(管道对象)
ObjectInputStream objectIn = null;
FileInputStream in = null;
try {
in = new FileInputStream(srcFile);
//需要传入一个FileInputStream对象
objectIn = new ObjectInputStream(in);
//读取对象
Object object = objectIn.readObject();
System.out.println(object);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
//如果文件不存在的话,需要把管道关闭
if(objectIn != null) {
try {
objectIn.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
class User implements Serializable{
}
常见异常
FileNotFoundException
文件找不到异常
FileInputStream fileInputStream = new FileInputStream("path");
ClassNotFoundException
从文件中读出的对象的类,在当前代码中找不到
FileInputStream fileInputStream = new FileInputStream("path");
ObjectInputStream objectIn = new ObjectInputStream(fileInputStream);
objectIn.readObject();
线程
Thread 线程类
获取当前线程
currendThread()
获取线程名称
getName()
System.out.println(Thread.currentThread().getName());
创建自定义线程
我们需要自定义线程类 重写run方法
然后创建线程对象使用start来运行线程
public class Java01_Variable {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//线程开始运行
myThread.start();
}
}
//自定义线程类 重写run方法
class MyThread extends Thread{
@Override
public void run() {
System.out.println(this.getClass().getName() + " "+Thread.currentThread().getName());
}
}
线程的生命周期
6种状态
串行和并发
线程的执行方式分为串行和并发
串行 join
join() 当前线程等待指定线程执行完,再继续执行
public class Java01_Variable {
public static void main(String[] args) throws InterruptedException {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
//当前线程等待指定线程执行完,再继续执行
t1.join();
t2.join();
System.out.println("主线程完毕");
//chapter02.MyThread1 Thread-0
//chapter02.MyThread2 Thread-1
//主线程完毕
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println(this.getClass().getName() + " "+Thread.currentThread().getName());
}
}
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println(this.getClass().getName() + " "+Thread.currentThread().getName());
}
}
并发
public class Java01_Variable {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
System.out.println("主线程完毕");
//主线程完毕
//chapter02.MyThread2 Thread-1
//chapter02.MyThread1 Thread-0
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println(this.getClass().getName() + " "+Thread.currentThread().getName());
}
}
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println(this.getClass().getName() + " "+Thread.currentThread().getName());
}
}
线程休眠
Thread.Sleep(1000);
lambda和内部类创建线程
每创建一个线程都需要去创建一个类太麻烦了,可以使用lambda表达式或者匿名内部类简化
//使用lambda表达式创建线程
Thread thread = new Thread(()->{
System.out.println("线程执行");
});
thread.start();
//可以使用实现Runbable接口的类创建线程,一般使用匿名内部类
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程二执行");
}
});
thread2.start();
线程池
线程池就是线程的容器,可以帮助我们管理线程
可以根据需要在启动时,创建一个或多个线程对象
Java中有四种常见线程池
1、创建固定数量的线程池
Executors.newFixedThreadPool(nums);
返回一个ExecutorService类,这是线程服务对象
//创建固定数量线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
//提交一个线程,使用匿名类实现Runnable接口
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
//线程1和2被使用了两次
//pool-1-thread-1
//pool-1-thread-3
//pool-1-thread-2
//pool-1-thread-1
//pool-1-thread-2
2、根据任务数量动态创建线程
ExecutorService executorService = Executors.newCachedThreadPool();
3、创建单一线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
4、定时调度线程
ExecutorService executorService = Executors.newScheduledThreadPool(3);
和第一个比较像,但是可以定义线程在什么时候执行
同步 synchronized
public class Java01_Variable {
public static void main(String[] args) throws InterruptedException {
//创建一个取号机
Num num = new Num();
User user = new User(num);
user.start();
Bank bank = new Bank(num);
bank.start();
}
}
//取号机
class Num{
}
class Bank extends Thread{
private Num num;
public Bank(Num num) {
this.num = num;
}
public void run(){
//到9点开门
synchronized (num){
try {
//模拟到9点
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("9:00,开门,开始叫号");
//通知wait的可以开始工作了
num.notifyAll();
}
}
}
class User extends Thread{
private Num num;
public User(Num num) {
this.num = num;
}
//取号
public void run(){
//同步去取号
synchronized (num){
System.out.println("我是号码一,银行还没开门,我需要等一会");
try {
//线程等待,如果没有人通知让我执行我就一直等
num.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("叫到我的号了,该我办业务了");
}
}
}
wait和sleep区别
线程安全问题
public class Java01_Variable {
public static void main(String[] args) {
User user = new User();
Thread t1 = new Thread(() -> {
user.name = "zhangsan";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(user.name);
});
t1.start();
Thread t2 = new Thread(() -> {
user.name = "lisi";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(user.name);
});
t2.start();
System.out.println("主线程执行完毕");
//主线程执行完毕
//lisi
//lisi
}
}
class User{
public String name;
}
反射
多态时限制,无法使用子类的成员方法
public class Java01_Variable {
public static void main(String[] args) {
//多态
User user = new Child();
user.test1();
//user.test2(); //多态访问子类成员方法会报错
}
}
class User{
public void test1(){
System.out.println("test1...");
}
}
class Child extends User{
public void test2(){
System.out.println("test2...");
}
}
字节码
那么想要使用子类的成员方法怎么办呢
我们在字节码文件夹中使用javap -v 类名 可以反编译.class文件,可以看到一些信息
使用反射获取信息
我们在写代码时通过反射也能获得这些信息
public class Java01_Variable {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//多态
User user = new Child();
user.test1();
//user.test2(); //多态访问子类成员方法会报错
//类对象 字节码文件对象
// ? extends User 泛型表示这是一个User的子类
Class<? extends User> aClass = user.getClass();
//获取包含package的完整类名
System.out.println(aClass.getName());
//获取不包含package的类名
System.out.println(aClass.getSimpleName());
//获取包名
System.out.println(aClass.getPackageName());
//获取父类
Class<?> superclass = aClass.getSuperclass();
System.out.println(superclass);
//获取类的接口
Class<?>[] interfaces = aClass.getInterfaces();
//获取类的属性
Field field1 = aClass.getField("xxxxx"); //只能取public权限
Field field2 = aClass.getDeclaredField("xxxxx");//所有权限都能取
//获取类的所有属性
Field[] fields = aClass.getFields(); //取public权限
Field[] declaredFields = aClass.getDeclaredFields();//所有权限都能取
//获取类的方法
Method method = aClass.getMethod("test2"); //只能取public权限
Method method2 = aClass.getDeclaredMethod("test2"); //所有权限都能取
Method[] methods = aClass.getMethods();
Method[] declaredMethods = aClass.getDeclaredMethods();
//获取构造方法
Constructor<? extends User> constructor = aClass.getConstructor();
Constructor<? extends User> declaredConstructor = aClass.getDeclaredConstructor();
Constructor<?>[] constructors = aClass.getConstructors();
//获取权限(修饰符):多个修饰符都在这个int中
int modifiers = aClass.getModifiers();
Modifier.isPublic(modifiers);
Modifier.isPrivate(modifiers);
}
}
class User{
public void test1(){
System.out.println("test1...");
}
}
class Child extends User{
public void test2(){
System.out.println("test2...");
}
}
类加载器
//获取类
Class<Student> studentClass = Student.class;
//获取类加载器 Java核心类库->平台类库->自定义类
ClassLoader classLoader = studentClass.getClassLoader();
//AppClassLoader
System.out.println(classLoader);
//PlatformClassLoader
System.out.println(classLoader.getParent());
//BootClassLoader
System.out.println(classLoader.getParent().getParent());
反射练习
不使用new对象,反射完成员工登录操作
public class Java01_Variable {
public static void main(String[] args) throws Exception {
//构造方法对象
Class empClass = Emp.class;
//或者Class<? extends Emp> empClass = new Emp().getClass();
//或者Class<?> empClass = Class.forName("chapter02.Emp");
//获得构造方法
Constructor declaredConstructor = empClass.getDeclaredConstructor();
//构建对象
Object emp = declaredConstructor.newInstance();
//获取对象属性
Field account = empClass.getField("account");
Field password = empClass.getField("password");
//给属性赋值 对象,值
account.set(emp,"admin");
password.set(emp,"admin");
//获取登录方法
Method login = empClass.getMethod("login");
//调用方法 invoke调用
Object result = login.invoke(emp);
System.out.println(result);
}
}
/**
* 员工
*/
class Emp{
public String account;
public String password;
public boolean login(){
if("admin".equals(account) && "admin".equals(password)){
return true;
}
return false;
}
}
反射异常
ClassNotFoundException
类找不到异常
如果类名错了,会报这个异常
Class<?> empClass = Class.forName("chapter02.Emp");
IllegalArgumentException
参数异常
newInstance()异常很多
//构建对象
Object emp = declaredConstructor.newInstance();
NoSuchMethodExcepiton
//获得构造方法
Constructor declaredConstructor = empClass.getDeclaredConstructor();
NoSuchFieldException
没有属性异常
//获取对象属性
Field account = empClass.getField("account");
IllegalAccessException
权限异常
//给属性赋值 对象,值
account.set(emp,"admin");
InvocationTargetException
调用目标异常
//调用方法 invoke调用
Object result = login.invoke(emp);