Java基础知识包括:基础语法、流程控制、方法详解、面向对象、异常机制、常用的类、网络编程、I/O流。
Java基础语法
1.1 注释,标识符,关键字
注释
- 单行注释 //
- 多行注释 /* 注释内容 */
- 文档注释/**注释内容 */
关键字
标识符
- JAVA所有的组成部分都需要名字,类名,变量名,以及方法名都被称为标识符
- 所有的标识符都应该以字母(A-Z,a-z),美元符($),或下划线(_)开始。
- 首字符之后可以是字母,美元符,下划线,数字的任意组合。
- 不能使用关键字作为方法名,类名或者变量名
- 标识符是区分大小写的
- 不建议标识符使用中文或者拼音
阿里巴巴JAVA开发手册对于命名的规定
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
- 所有编程相关的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /PO / UID 等。
- 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格。
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类
命名以它要测试的类的名称开始,以 Test 结尾。 - 类型与中括号紧挨相连来表示数组。(int[] arrayDemo。)
- POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
- 避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低。
1.2 数据类型
强类型语言
- 要求变量的使用要严格符合规定,所有的变量都需要先定义,再使用。
- 安全性高,速度较慢。
弱类型语言
vb,javascript等
JAVA的数据类型分为两大类
- 基本数据类型(primitive type)
- 引用数据类型(reference type)
基本数据类型
四类:
整数类型,浮点数类型,字符类型,boolean类型
八种:
byte(1个字节,-128 ~ 127) 二进制:(1000 0000 ~ 0111 1111 )
short(两个字节,-32768 ~ 32767)二进制:(1000 0000 0000 0000 ~ 0111 1111 1111 1111)
int(四个字节,-2147483648 ~ 2147483647)
long(八个字节,-9223372036854775808 ~ 9223372036854775807)
float(四个字节)
double(八个字节)
char(两个字节)
boolean(1位,取值只有(true,false)(0为false,非0为true)
通过基本类型对应的包装类可以查看数据范围
引用数据类型
类,接口,数组
-
定义long数据类型时,一般在末尾加L。比如:long num = 10000000000L
-
定义float时,在末尾加F。比如:float num2 = 3.1415F;
-
字符串不是基本数据类型,是一个类
-
定义字符:char ch = ‘A’; ,char ch2 = ‘中’;要用单引号,用双引号会报错
什么是字节
- 位(bit):是计算机内部数据存储的最小单位,0011 1011是一个八位二进制数
- 字节(Byte):是计算机中数据处理的基本单位,用B表示
- 1B(字节) = 8bit(位)
- 字符:是指计算机中使用的字母,数字,字和符号
整数拓展
不同进制的数在JAVA中如何表示
二进制0b
开头:0b0011
八进制0
开头:0123
十进制:正常:134
十六进制0x
开头:0x345F
//不同进制的表示形式
int i = 0b10; //2
int i1 = 010; //8
int i2 = 10; //10
int i3 = 0x10; //16
浮点数拓展
银行业务一般不用float和double表示,使用BigDecimal
数学工具类来表示
float 舍入误差,接近但不等于
最好完全避免使用浮点数进行比较
public class demo2 {
public static void main(String[] args) {
float a = 0.1f;
double b = 0.1/1;
System.out.println(a); //0.1
System.out.println(b); //0.1
System.out.println(a == b); //false
}
}
==============================================
public class demo3 {
public static void main(String[] args) {
float a = 12356648152164f;
float b = a+1;
System.out.println(a); //1.23566482E13
System.out.println(b); //1.23566482E13
System.out.println(a == b); //true
}
}
字符拓展
- 所有字符本质还是数字
- 编码:Unicode(2个字节) 共有65536个字节(1000 0000 0000 0000 ~ 0111 1111 1111 1111)
- 用十进制表示即(-32768 ~ 32767)加上0就是32768*2=65536个字节
- 转义字符 \t :制表符相当于tab键
public class demo3 {
public static void main(String[] args) {
char c1 = 'a';
//十六进制的0061转换为10进制就是97,所以'\u0061'就是'a'
char c2 = '\u0061';
System.out.println((int)c1); //97
System.out.println(c2); //a
}
}
字符串扩展
public class demo3 {
public static void main(String[] args) {
String sa = new String("Hello");
String sb = new String("Hello");
System.out.println(sa==sb); //false比较的是内存地址值,两个对象的内存地址不同
String sc = "Hello";
String sd = "Hello";
System.out.println(sc ==sd); //true
}
}
布尔值扩展
public class demo3 {
public static void main(String[] args) {
boolean flag =true;
if(flag){} 等价于下面的,一般使用该方法
if(flag==true){}
}
}
1.3 类型转换
由低到高
byte,short,char---->int---->long---->float----->double
运算中,不同类型的数据需要转换为同一类型之后才能进行运算
- 强制类型转换(高–>低) (类型)变量名
- 自动类型转换 (低–>高)
- 强制类型转换的时候可能存在内存溢出或者精度问题
public class demo4 {
public static void main(String[] args) {
//内存溢出和精度问题
int i = 128;
byte i1 =(byte)i;
System.out.println(i1); //内存溢出,byte最多存放到127
System.out.println((int)4.57f); //4 精度问题
System.out.println((int)-5.6877); //-5
}
}
常见的问题,防止内存溢出,在出结果之前就对其进行类型转换
public class demo5 {
public static void main(String[] args) {
int money = 10_0000_0000;
int year = 20;
long total = money*year;
System.out.println(total); //内存溢出,-1474836480
}
}
解决方法,出结果之前就转换为long类型
public class demo5 {
public static void main(String[] args) {
int money = 10_0000_0000;
int year = 20;
long total = money*(long)year;
System.out.println(total); //20000000000
}
}
1.4 变量和常量
变量
可以变化的量
- JAVA是强类型语言,每个变量都必须为其指定数据类型
- JAVA变量是程序中最基本的存储单元,要素有:变量名,变量类型,作用域
变量作用域
- 类变量 有static修饰
- 实例变量
- 局部变量 作用在方法中的变量
public class demo6 {
//类变量,从属于类,用static修饰,可以在方法中直接使用
static double salary=2500;
//实例变量,从属于对象,如果不初始化,会有默认值
//布尔值默认false
//除了基本类型,其他类型默认值都是null
String name;
public static void main(String[] args) {
//局部变量,必须声明和赋值
int i = 10;
System.out.println(i);
//使用实例变量
demo6 d = new demo6();
String name = d.name;
System.out.println(name); //null
//使用类变量
System.out.println(salary); //2500.0
}
}
常量
初始化后不能再改变的值
所谓的常量可以理解为一种特殊的变量,它的值被设定后,在程序运行过程中不能被改变。
final 类型 常量名 = 值;
final diouble PI = 3.14;
常量名一般使用大写字符表示
//修饰符不区分先后顺序
static final double PI = 3.14;
final static double PI = 3.14;
变量的命名规范
- 所有的变量,方法名,类名,都应该见名知意。
- 类成员变量:首字母小写和驼峰命名原则,monthSalary
- 局部变量:首字母小写和驼峰命名原则,name
- 常量:大写字母和下划线,MAX_VALUE
- 类名:首字母大写和驼峰命名规则,GoodMan
- 方法名:首字母小写和驼峰命名原则,getName()
1.5 运算符
JAVA支持的运算符有:
- 算术运算符:+, -, *, /, %, ++, --(自增运算等级大于乘除)
- 赋值运算符:=
- 关系运算符:>, <, >=, <=, ==, !=, instance of
- 逻辑运算符:&&, ||, !
- 位运算符:&, |, ^, ~, >>, <<, >>>
- 条件运算符:? :
- 扩展赋值运算符:+=, -=, *=, /=
不同类型之间进行数学运算
整数运算:
如果多个不同类型的整数进行运算,有一个数是long类型,结果就是long类型
如果没有long的时候,结果都是int
浮点数运算
如果多个不同类型的数进行运算,有一个数是double类型,结果就是double类型
如果没有double的时候,有一个是float,结果就是float类型
public class Demo1 {
public static void main(String[] args) {
long a = 12456789876L;
int b = 500;
byte c = 1;
double d = 5.1234862145;
float e = 0.123456F;
System.out.println(d+b); //结果是double
System.out.println(d+a);//结果是double
System.out.println(e+b);//结果是float
System.out.println(e+a);//结果是float
System.out.println(a+b); //结果是long类型
System.out.println(a+c); //结果是long类型
System.out.println(b+c); //结果是int类型
}
}
关系运算符的结果都是boolean类型
int a = 10;
int b = 20;
System.out.println(a != b); // true
自增,自减,幂运算
public class Demo2 {
public static void main(String[] args) {
int a = 3;
int b = a++; //先运算,再自增,先把a赋值给b,a再加1,此时a=4,b=3
int c = ++a; //先自增,再运算,a先加1,再讲结果赋值给c,所以,a=5,c=5
System.out.println(a); //5
System.out.println(b); //3
System.out.println(c); //5
//一些其他的数学运算,会借助工具类来实现,比如求2的5次方
double pow = Math.pow(2, 5);
System.out.println(pow); //32.0
}
}
逻辑运算
(a>b)&& (a++ <c):如果a>b为假,则不会执行后半部分内容,称为短路运算。
public class Demo3 {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
System.out.println(a && b); //false
System.out.println(a || b); //true
System.out.println(!b); //true
//短路运算,如果与运算前面那个是false,则不会执行后半部分内容
int c = 5;
boolean d = (c<4) && (c++>4); //true
System.out.println(d); //false
System.out.println(c); //5
}
}
位运算
与运算(&) 或运算(|)非运算(~)异或运算(^)
public class Demo4 {
public static void main(String[] args) {
int a = 52; //00110100
int b = -100; //10011100
System.out.println(a&b); // 十进制:20 二进制:00010100
}
}
-----------------------------------------------------
与运算:两个都为1,结果为1,任意有一个为0,结果都是0
1001 0110
1010 1100
----------
1000 0100
================
或运算:一个为1,结果就是1
1001 0110
1010 1100
----------
1011 1110
================
异或运算:不相同就是1,相同就是0
1001 0110
1010 1100
----------
0011 1010
================
非运算:对其取反
1001 0110
----------
0110 1001
字符串连接符
如果让一个空字符串和一个数学表达式进行相加,位置不同,结果也不同
public class Demo4 {
public static void main(String[] args) {
int a = 52;
int b = -100;
System.out.println(""+a+b); //"52-100"
System.out.println(a+b+""); //"-48"
}
}
三元运算符
public class Demo5 {
public static void main(String[] args) {
//三元运算符
int a = 5;
int b = a > 3 ? 10: 20;//如果a>3表达式结果就是10,否则就是20
System.out.println(b); //10
}
}
JAVA运算的优先级,有括号先括号,没括号先乘除后加减。
1.6 自动生成javadoc文档
包机制
为了更好地组织类,JAVA提供了包机制,用于区别类名的命名空间
- 一般利用公司域名倒置作为包名
- 为了使用特定包下的成员变量或者方法,需要先使用import进行导包
- import com.rm.* 导入该包下的所有类
JavaDoc
javadoc命令是用来自动生成自己的API文档的。
参数信息:
- @author 作者名
- @version 版本号
- @since 指明需要最早使用的jdk版本
- @param 参数名
- @return 返回值
- @throws 异常抛出情况
可以加在类或者方法上
/**
* @author rm
* @version 1.0
* @since 1.8
*/
public class Demo6 {
String name;
/**
* @param name
* @return
* @throws Exception
*/
public String getName(String name)throws Exception{
return name;
}
}
在java文件所在的目录下运行该命令
- 第一个参数 -encoding UTF-8 表示你的源代码(含有符合 JavaDoc 标准的注释)是基于 UTF-8 编码的,以免处理过程中出现中文等非英语字符乱码;第二个参数 -charset UTF-8 表示在处理并生成 JavaDoc 超文本时使用的字符集也是以 UTF-8 为编码。
结果:
在IDEA中生成JavaDoc文档
Java流程控制
2.1 Scanner类
Java.util.Scanner是Java5新特性,可以通过Scanner类获取用户的键盘输入
基本语法
Scanner s = new Scanner(System.in);
通过Scanner类的next()和nextLine()方法获取输入的字符串,在读取前一般需要先hasNext()或hasNextLine()来判断是否还有输入数据。
next
public class Demo1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if(scanner.hasNext()){
String s = scanner.next();
//键盘输入hello word,输出结果是:hello
System.out.println("输出结果是:"+s);
}
凡是有关io操作的,使用完毕都要释放资源
scanner.close();
}
}
nextLine
public class Demo2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if(scanner.hasNextLine()){
String s = scanner.nextLine();
//键盘输入hello world,输出结果是:hello world
System.out.println("输出结果是:"+s);
}
scanner.close();
}
}
next和nextLine的区别
next()
:用的较少
- 一定要读取到有效字符后才可以结束输入
- 对输入有效字符之前的空格,next()方法会自动将其去掉
- 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
- next()不能得到带有空格的字符串
nextLine()
- 以Enter为结束符,nextLine()方法返回的是回车之前的所有字符
- 可以获得空白
练习:从键盘输入几个数,求其总和和平均值
public class Demo4 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int sum = 0;
int i = 1;
//当输入的不是整数时,退出循环
while(scanner.hasNextInt()){
int num = scanner.nextInt();
sum = sum +num;
i++;
}
System.out.println("总和是:"+sum);
System.out.println("平均数是:"+sum/i);
}
}
2.2 顺序结构
- JAVA的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行
- 顺序结构是最简单的算法结构
2.3 选择结构
if单选择结构
if(布尔表达式){
布尔表达式为true将执行的语句
}
if双选择结构
if(布尔表达式){
布尔表达式为true将执行的语句
}else{
布尔表达式为false将执行的语句
}
if多选择结构
if(布尔表达式1){
布尔表达式1为true将执行的语句
}else if(布尔表达式2){
布尔表达式2为true将执行的语句
}else if(布尔表达式3){
布尔表达式3为true将执行的语句
}else{
三个表达式都为false将执行的语句
}
嵌套的if结构
if(布尔表达式1){
布尔表达式1为true将执行的语句
if(布尔表达式2){
布尔表达式2为true将执行的语句
}
}
switch多选择结构
switch case语句判断一个变量与一系列值中的某个值是否相等,每个值称为一个分支
switch语句中的变量
- byte,short,int,char
- Java7之后,支持字符串String
- case标签必须为字符串常量
public class Deno5 {
public static void main(String[] args) {
char grade = 'B';
switch (grade){
//如果没有break,会出现case穿透问题
case 'A':
System.out.println("A");
break;
case 'B':
System.out.println("B");
break;
case 'C':
System.out.println("C");
break;
default:
System.out.println("输入结果不规范");
}
}
}
break
:退出循环,不会执行下面的语句
default
:判断内容都不符合才执行的语句
Switch语句字符串实现原理
查看编译后的字节码文件
本质上比较的是字符串的hashCode值
public class Deno5 {
public Deno5() {
}
public static void main(String[] args) {
String grade = "优秀";
byte var3 = -1;
switch(grade.hashCode()) {
case 658856:
if (grade.equals("优秀")) {
var3 = 1;
}
break;
case 691634:
if (grade.equals("及格")) {
var3 = 0;
}
}
switch(var3) {
case 0:
System.out.println("及格");
break;
case 1:
System.out.println("优秀");
break;
default:
System.out.println("输入不规范");
}
}
}
2.4 循环结构
while循环
- 只要布尔表达式为true,循环就会一直执行下去
- 需要一个让表达式失效的方式让循环停下来
- 少部分情况需要让循环一直执行,比如服务器的请求响应监听等
- 循环条件一直为true,会造成死循环,影响程序性能或导致程序崩溃
while(布尔表达式){
布尔表达式为真循环的内容
}
do…while循环
- 对于while语句来说,如果不满足条件,while语句中的内容永远不会执行,但有时候需要即使不满足条件,也需要执行一次。
- do…while循环最少会执行一次
do{
循环语句
}while(布尔表达式)
for循环
- for循环语句是支持迭代的一种通用结构,
是最有效,最灵活的循环结构
- for循环执行的次数是在执行前就确定的
public class Test1 {
//九九乘法表
public static void main(String[] args) {
for(int i = 1; i<=9;i++){
for(int j = 1; j<=i ; j++){
System.out.println(j+"*"+i+"="+(i * j));
}
}
}
}
for循环形式的死循环
//死循环
for( ; ; ){
...
}
增强for循环(JDK1.5之后)
public static void main(String[] args) {
int[] nums ={1,2,3,4,5};
for(int x : nums){
System.out.println(x); //1,2,3,4,5
}
}
2.5 break和continue
break
- 在任何循环语句的主题部分,均可用break控制循环的流程,
break用于强行退出循环
,不执行循环中剩余的语句
continue
- continue语句用在循环语句体中,
用于终止某次循环过程
,即跳出循环体未执行的语句,接着进行下一次是否执行循环的条件判定。
Java方法详解
3.1 什么是方法
JAVA方法是语句的集合,这些语句在一起执行一个功能。
- 方法是解决一类问题的步骤的有序组合。
- 方法包含于类或者对象中
- 方法在程序中被创建,在其他地方被引用
设计方法的原则
- 方法的本意是功能块,就是实现某个功能块的语句集合。在设计方法的时候,要保证方法的原子性,就是
一个方法只用来完成一个功能
,有利于以后的扩展。
3.2 方法的定义和调用
定义方法
方法包含一个方法头和一个方法体,一个方法的组成部分包括:
修饰符:
修饰符是可选的,告诉编译器如何调用该方法,定义了该方法的访问类型返回值类型:
方法可能会返回值,是用来定义方法返回数据的类型,没有返回值的方法返
回值类型为void。方法名:
是方法的实际名称,方法名和参数共同构成方法签名。参数类型:
参数像是一个占位符,当方法被调用时,传递值给参数,这个值被称为实参或变量,参数列表是指方法的参数类型,顺序,和参数的个数,参数是可选的,方法可以不包含参数。- 形式参数:在方法被调用时用于接受外界的数据
- 实参:调用方法时传递给方法的参数
方法体:
方法体包含具体的语句,定义该方法的功能
public class Demo {
public static void main(String[] args) {
int i = compare(8, 5);
System.out.println("两个数中比较大的是:"+i);
}
public static int compare(int a, int b){
int result = 0;
if(a==b){
System.out.println("两个数一样大");
return 0; //return也有终止方法的功能
}
if(a>b){
result = a ;
}else {
result = b;
}
return result ;
}
}
调用方法
静态方法调用:
类名.方法名(实参列表)非静态方法调用:
对象名.方法名(实参列表)- Java支持两种调用方法的方式,根据方法是否有返回值来决定
- 当方法返回一个值的时候,方法调用通常被当做一个值
- 如果方法返回值是void,方法调用就是一条语句。
int a = max(10,20);
3.3 方法的重载
重载就是在一个类中,有相同的函数名称,但参数类型不同的方法
方法重载的规则
- 方法名称必须相同
- 参数列表必须不同(个数不同,类型不同,参数排列顺序不同)
- 方法的返回类型可以相同也可以不同
- 仅返回值类型不同不足以构成方法的重载
public int compare(int a, int b){
if (a>=b){
return a;
}else {
return b;
}
}
public int compare(int a,int b,int c){
if (a>b && a>c){
return a;
}else if(b>a && b>c){
return b;
}else {
return c;
}
}
实现理论
- 方法名称相同时,编译器会根据调用方法的参数个数,参数类型等逐个去匹配,已选择对应的方法,确定方法,执行方法之后才会有返回值,找不到对应的方法会报错。
3.4 命令行传参
有时候需要运行一个程序时再传递给它消息,需要靠传递命令行参数给main()方法来实现
public class Demo {
public static void main(String[] args) {
for (int i = 0; i <args.length ; i++) {
System.out.println("args("+i +")"+args[i]);
}
}
}
注意:需要先cd …/ 退回到src目录下,才能执行这条语句。类名为全限定类名。
3.5 可变参数(不定项参数)
- JDK1.5开始。java支持传递同类型的可变参数给一个方法
- 在方法声明中,在指定参数类型后加一个省略号…
- 一个方法中只能存在一个可变参数,它必须是方法的最后一个参数,任何普通的参数都必须在它之前声明。
//求几个数最大值得方法
public class Demo {
public static void main(String[] args) {
pringMax(2,5,8,15); //15.0
}
public static void pringMax(double... numbers){
if(numbers.length==0){
System.out.println("没有参数输入");
return;//退出方法体
}
double result = numbers[0];
for (int i = 0; i <numbers.length ; i++) {
if(numbers[i] > result){
result=numbers[i];
}
}
System.out.println("最大的数是:"+result);
}
}
3.6 递归
- 递归就是A方法调用A方法
- 利用递归可以用简单的代码来解决较为复杂的问题,它通常把一个大型的复杂的问题,转换为一个与原问题相似但规模较小的问题来解决。递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复运算,大大减少了程序的代码量。
- 递归的能力在于用有限的语句来定义对象的无限集合。
递归结构
递归头:
什么时候不调用自身方法,如果没有头,会陷入死循环。递归体:
什么时候需要调用自身方法。
使用递归求一个数的阶乘
public class Demo4 {
public static void main(String[] args) {
int result = method(5); //120
System.out.println(result);
}
//求阶乘
public static int method(int i){
if(i ==1 ){
return 1;
}else{
return i * method(i-1);
}
}
}
递归在运行期间会占用大量的栈空间,会带来大量的函数调用,深度越大,时空性越差。基数较小的时候可以使用递归。
编写一个计算器,实现加减乘除
public class Demo {
public static void main(String[] args) {
double x = 0;
double y = 0;
String method = "+";
while(true){
Scanner scanner = new Scanner(System.in);
method = scanner.next();
switch (method){
case "+":
x = scanner.nextDouble();
y = scanner.nextDouble();
System.out.println(add(x, y));
break;
case "-":
x = scanner.nextDouble();
y = scanner.nextDouble();
System.out.println(sub(x, y));
break;
case "*":
x = scanner.nextDouble();
y = scanner.nextDouble();
System.out.println(mul(x, y));
break;
case "/":
x = scanner.nextDouble();
y = scanner.nextDouble();
System.out.println(div(x, y));
break;
}
}
}
public static double add(double a, double b){
return a+b;
}
public static double sub(double a, double b){
return a-b;
}
public static double mul(double a, double b){
return a*b;
}
public static double div(double a, double b){
return a/b;
}
Java面向对象编程
对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个系统,但是,具体到每一步操作,仍然需要面向过程的思想来解决。
4.1 什么是面向对象
面向过程
- 步骤清晰简单,第一步需要做什么,第二步需要做什么。
- 面向过程适合处理一些简单的问题
面向对象
- 使用
分类
的思维模式,解决一个问题都需要哪些类,然后对这些类单独思考,最后,才对某个分类下的细节使用面向过程。 - 面向对象适合处理一些复杂的问题,适合处理一些需要多人合作的问题。
面向对象编程(Object-Oriented Programming)[OOP]
-
本质:
以类的方式组织代码,以对象的形式封装数据
-
三大特性:封装性、继承性、多态性
类和对象
- 从认识论角度来说是先有对象后有类,对象是具体的事物,类是抽象的,是对对象的抽象
- 从代码运行考虑是先有类,再有的对象,类是对象的模板。
4.2 方法回顾
break和continue、return的区别
- break在switch,while语句中表示跳出循环,该循环结束。
- continue表示跳出该循环,继续执行下一次循环。
- return表示方法的结束,方法没有返回值可以使用 return;
静态方法和非静态方法
静态方法
:static关键字修饰,跟随类一起加载,类存在,静态方法就存在。可以直接通过类名.方法名调用。非静态方法
:没有static修饰,类实例化(对象创建)之后才存在,只能先通过关键字new出一个对象之后,使用对象名.方法名使用。- 静态方法只能调用静态方法,不能调用非静态方法
- 非静态方法可以调用静态方法,因为静态方法是在非静态方法出现前就已经存在的。
4.3 对象的创建分析
使用new关键字创建对象
- 使用new关键字的时候,会给创建好的对象进行默认的初始化以及对类中构造器的调用。
- 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。
构造器特点
- 必须和类的名字相同
- 必须没有返回值类型,也不能有void
构造器的作用
- new 关键字本质是调用构造方法(构造器)
- 构造器的作用是用来初始化对象值。
public class Student {
private String name;
//无参构造器
public Student() {
}
//有参构造器
public Student(String name) {
this.name = name;
}
}
4.4 面向对象三大特性
4.4.1 封装
为什么需要封装
- 程序设计要追求
高内聚,低耦合
。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉。低耦合就是仅暴露少量的方法供外部使用。 - 封装:(数据的隐藏)通常,应禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问,称作信息的隐藏。
属性私有:
通过get/set方法来访问类的属性。
封装的作用
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 系统可维护性增加了
- 可以通过set方法实现一些数据的过滤
4.4.2 继承
什么是继承
-
继承的本质是对某一批类的抽象,从而实现对现实世界更好地建模。
-
Java中类只有单继承,没有多继承,只能有一个爹。
-
继承是类和类之间的关系,除此之外,类与类之间的关系还有
依赖,组合,聚合等
-
继承关系中的两个类,一个为子类(派生类),一个为父类(基类)。
-
ctrl + h 会打开该类的继承树,可以查看该类的父类,以及父类的父类…
-
如果一个类被final修饰,则该类无法被任何类所继承。
super-this关键字
- 通过super关键字访问父类的属性
super关键字注意
- super调用父类的构造方法,必须在构造方法的第一个 (不写,默认调用)
- super只能出现在子类的方法或构造方法中
- super和this不能同时调用构造方法
this关键字注意
- 代表的对象不同,this表示本身调用者这个对象,super代表父类对象的引用。
- this在没有继承的情况下也能使用,super不能。
- 构造方法 this():本类的构造。super():父类的构造
方法的重写
为什么需要重写
- 父类的功能,子类不一定需要,或者不一定满足
方法重写的要求
- 重写都是方法的重写,和属性无关。
- 需要有继承关系,子类重写父类的方法。
- 方法名必须相同,但是方法体要不同。
- 参数列表必须相同
- 修饰符,范围可以扩大,但是不能缩小public>protected>default>private
- 抛出的异常范围,可以被缩小,但是不能被放大
4.4.3 多态
什么是多态
- 即同一方法可以根据发送对象的不同而采用多种不同的行为方式
- 一个对象的实例类型是确定的,但可以指向对象的引用类型有很多。
对象能执行哪些方法主要看左边的类型。如果子类重写了父类的方法,则通过引用父类创建出来的子类对象,调用的依旧是子类方法
//父类
public class Father {
public void eat(){
System.out.println("father");
}
public void run(){
System.out.println("跑");
}
}
=======================================
//子类
public class Son extends Father{
@Override
public void eat() {
System.out.println("son");
}
public static void main(String[] args) {
Father son = new Son(); //通过多态创建出来的对象
son.run();//跑,子类没有该方法,调用的是父类的方法
son.eat();//son
}
}
多态存在的条件
- 有继承关系
- 子类重写父类方法
父类引用指向子类对象
Father a = new Son();
不能被重写的方法:
- static 方法,属于类,不属于实例
- final 常量
- private方法
instanceof关键字
判断一个对象和一个类是否有继承关系
public class Son extends Father {
public static void main(String[] args) {
Father son = new Son();
boolean b = son instanceof Son;
boolean b1 = son instanceof Father;
boolean b2 = son instanceof Object;
System.out.println(b); //true
System.out.println(b1); //true
System.out.println(b2); //true
}
}
类型转换
- 把子类转换为父类,向上转型,不用强制转换。(多态)
Son s = new Son();
Father f = s; //转换类型
- 父类转换为子类,向下转型,需要强制转换,强制转换可能会丢失一些方法。
Father f = new Son();
Son s = (Son)f; //强制转换
4.5 static关键字
静态代码块、匿名代码块、构造方法的执行顺序
public class Demo1 {
{
System.out.println("匿名代码块");
}
static {
System.out.println("静态代码块");
}
public Demo1() {
System.out.println("构造方法");
}
public static void main(String[] args) {
Demo1 d = new Demo1();
System.out.println("============");
Demo1 d1 = new Demo1();
}
}
/*
静态代码块
匿名代码块
构造方法
============
匿名代码块
构造方法
*/
结论
- 加载类的同时执行静态代码块,所以在程序运行过程中只执行一次。
- 创建对象的时候先执行匿名代码块,再执行构造方法,创建一个对象,执行一次。
- 匿名代码块通常用来给属性赋初始值。
拓展:静态导入包
- 再使用Math类中的方法时每次都使用类名.方法名,代码有些重复
- 可以使用静态导入包的方法,只使用方法名就行
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class StaticPack {
public static void main(String[] args) {
//System.out.println(Math.random()); 正常书写
System.out.println(random()); //静态导入包后
System.out.println(PI);
}
}
4.6 抽象类和接口
4.6.1 抽象类
-
abstract修饰符可以用来修饰方法也可以用来修饰类,如果修饰方法,该方法就是抽象方法,如果修饰类,该类就是抽象类。
-
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明是抽象类。
-
抽象类
不能使用new关键字创建对象
,它是用来让子类继承的。 -
抽象方法
只有方法的声明,没有方法的实现
,它是用来让子类实现的 -
子类继承抽象类,那么就
必须要实现抽象类没有实现的抽象方法
,否则该子类也要声明为抽象类。 -
抽象类也有默认的空构造器
4.6.2 接口
普通类:
只有具体实现
抽象类:
具体实现和规范(抽象方法)都可以有
接口:
只有规范 ,约束和实现分离,面向接口编程
为什么需要接口
-
其实接口和抽象类的作用相似,但是在java中只允许单继承,一个类只能继承一个抽象类
-
一个类可以实现多个接口
-
接口就是规范,定义的一组规则
-
OOP(面向对象)的精髓,是对对象的抽象,最能体现这一点的就是接口。设计模式所研究的,就是如何合理的去抽象。
-
声明类的关键字是class,声明接口的关键字是interface,实现接口使用implement
-
接口中的所有定义都是公共的,抽象的。public abstract可以不写
-
接口中可以定义属性 publicstatic final name =“张三”;但通常不会在接口中定义属性。
-
接口不能被实例化,接口中没有构造方法
-
implements可以实现多个接口,用逗号隔开
4.7 内部类
什么是内部类
内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,B类相对于A类来说就是内部类,A类对于B类来说就是外部类。
成员内部类
- (内部类可以直接访问外部类的私有属性)
//外部类
public class Outer {
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
//内部类,可以直接访问外部类的私有属性
public class Inner{
public void getId(){
System.out.println(id);
}
}
//main方法
public static void main(String[] args) {
Outer outer = new Outer();
//使用内部类的方法,需要先创建外部类的对象
Inner inner = outer.new Inner();
inner.getId(); //10
}
}
静态内部类
//外部类
public class Outer {
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
//静态内部类,不能直接访问外部类的私有属性,因为先加载的静态内部类,再有的外部类属性
public static class Inner{
public void getId(){
//System.out.println(id);
}
}
//main方法
public static void main(String[] args) {
Outer outer = new Outer();
//使用内部类的方法,需要先创建外部类的对象
Inner inner = outer.new Inner();
inner.getId(); //10
}
}
局部内部类
- (一个类在另一个类的方法中被创建)
public class Outer {
public void out(){
//局部内部类,在一个类方法中的类。
class Inner{
public void in(){
}
}
}
}
匿名内部类
- (和匿名对象)
public class Test {
public static void main(String[] args) {
//匿名对象,创建一个对象,但是不给其赋予变量名
new Other().method();
//匿名内部类,该类实现了接口,但是没有名字,只能使用一次。
new UserService(){
@Override
public void hello() {
}
};
}
}
interface UserService{
void hello();
}
class Other{
public void method(){};
}
Java异常机制
5.1 什么是异常
- 实际工作中,遇到的情况不可能是完美的。比如:写的一个模块,用户输入不合法,程序要打开一个文件,但是这个文件可能不存在等。
- 程序在运行过程中,非常可能遇到上面的情况,叫做异常(Exception)
- 异常是指程序运行中出现的不期而至的各种状况:文件找不到,网络连接失败,输入无效
- 异常发生在程序运行期间,他影响了程序的正常执行流程。
异常分类
检查性异常
,最具代表性的检查性异常是用户输入错误引起的异常,这是程序员无法预见的。例如要打开一个文件,改文件确不存在,就发生了一个异常。这些异常在编译时不能简单地被忽略。运行时异常
,运行时异常是可被程序员避免的异常,与检查性异常相反,运行时异常通常在编译时可以被忽略。错误
,错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略,当栈溢出时,就会出现一个错误,在编译时是无法被检查到的。
5.2 异常体系结构
Java把异常当做一个对象来处理,并定义了一个基类java.lang.Throwable作为所有异常的超类。
在Java API中定义了许多异常类,分为两大类。错误Error
和异常Exception
。
ERROR 错误
- Error类对象有JAVA虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
- JAVA虚拟机运行错误(Virtual MachineError),当JVM不在有继续执行操作所需要的的内存时,将出现(OutOfMemorError),这些错误发生时,JVM一般会选择线程终止
- 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError),链接错误(LinkageError),这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
Exception 异常
在Exception分支中有一个重要的子类RuntimeException (运行时异常)
-
ArrayIndexOutOfBoundsException (数组下标越界)
-
NullPointerException (空指针异常)
-
ArithmeticException (算术异常)
-
MissingResourceException (丢失资源)
-
ClassNotFoundException (找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
Error和Exception的区别: Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM) 一般会选择终止线程; Exception通常情况 下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
5.3 Java异常处理机制
- 抛出异常 throw
- 捕获异常 try catch
捕获异常
可以捕获多个异常,异常从小到大
public class Excep {
public static void main(String[] args) {
int a = 1;
int b = 0;
try{
System.out.println(a/b);
}catch (Exception e){
e.printStackTrace(); //该语句表示打印错误的栈信息。
}catch (Throwable t){
System.out.println("Throwable");
}finally {
System.out.println("被除数不能为0");
}
}
}
IDEA自动生成捕获异常代码,选择可能出现异常的代码句,ctrl+alt+t
抛出异常
throw
public class Test {
public static void main(String[] args) {
new Test().method(1,0);
}
public void method(int a,int b){
if(b==0){
//主动抛出异常,一般会在方法中使用
throw new ArithmeticException();
}
//System.out.println(a/b);
}
}
throws
如果一个方法抛出异常,那么在调用这个方法时需要对其进行捕获处理
public class Test {
public static void main(String[] args) {
try {
new Test().method(1,0);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
//出现异常限制性finally语句,在执行处理异常语句
System.out.println("finally");
}
System.out.println("后面的语句 ");
}
public void method(int a,int b) throws ArithmeticException{
System.out.println(a/b);
}
/*
finally
java.lang.ArithmeticException: / by zero
后面的语句
at oop.demo7.Test.method(Test.java:16)
at oop.demo7.Test.main(Test.java:6)
*/
5.4 自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况,除此之外,程序员还可以自定义异常,自定义异常类只需继承Exception类即可。
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句进行捕获并处理,否咋在方法的声明处通过throws关键字指定要抛出给方法调用者的异常,继续下一步操作。
- 在出现异常方法的调用者中捕获并处理异常
自定义异常
public class MyException extends Exception {
private int num ;
public MyException(int a){
this.num=a;
}
@Override
public String toString() {
return "MyException{" +
"num=" + num +
'}';
}
}
使用该异常
public class Test {
public static void main(String[] args) {
try {
new Test().method(11);
} catch (MyException e) {
e.printStackTrace();
}
System.out.println("后面的代码");
}
public void method(int a) throws MyException {
if(a>10){
throw new MyException(a);
}
}
}
实际应用中的经验总结
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch (Exception) 来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch ,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源
Java常用的类
6.1 内部类
一个类,定义在另一个类中,比如head类定义在body类中。
public class Body {
class Heade{
}
}
Java中内部类有四种:成员内部类
,静态内部类
,局部内部类
,匿名内部类
。
内部类特点:
- 内部类也会被编译成class文件
- 内部类可直接访问外部类的私有成员变量。
- 可为外部类提供必要的内部功能组件。
6.1.1 成员内部类
- 在类的内部定义,与实例变量,实例方法同级别的类
- 是外部类的一个实例部分,创建内部类的时候,必须依赖外部类对象创建
public static void main(String[] args) {
Body body = new Body();
Heade heade = body.new Heade();
}
- 当外部类,内部类存在重名属性时,会优先访问内部类属性。
- 成员内部类不能定义静态成员,但是能定义静态常量
public static final String ADDRESS ="北京";
同名情况下如果需要访问外部类的成员属性 外部类.this.成员属性
public class Out {
private String name = "张三";
class Inner{
private String name ="李四";
public void getName(){
System.out.println(name); //李四
System.out.println(Out.this.name); //张三
}
}
public static void main(String[] args) {
Inner inn = new Out().new Inner();
inn.getName();
}
}
6.1.2 静态内部类
- 不依赖外部类对象,可直接创建或通过类名访问,可声明静态成员。(相当于一个外部类)
- 只有静态内部类可以使用static修饰,其余类不能用static修饰
public class Out {
private String name = "张三";
static class Inn{
private String name = "李四";
private static int age = 23;
public void getName(){
System.out.println(this.name);
//使用外部类的成员属性
Out out = new Out();
System.out.println(out.name);
//使用自己类的静态成员属性
System.out.println(Inn.age);
}
}
public static void main(String[] args) {
//可以正常写
new Inn().getName();
//也可以描述类关系
new Out.Inn().getName();
}
}
6.1.3 局部内部类
- 定义在外部类方法中,作用范围和创建对象范围仅限于当前方法
- 局部内部类访问外部类当前方法中的局部变量时,因无法保证变量的生命周期与自身相同,变量必须修饰为final。(JDK1.8之后默认添加final)
- 有局部内部类的方法在调用完毕时,保存在栈内存中的变量会消失,但是new出来的局部内部类保险在堆内存中,不会消失。所以方法中的变量必须用final修饰为常量。
public class Out {
private String name = "小白";
public void getName(){
final int age =5;
//局部内部类
class Inn{
private String name = "小葵";
public void getName2(){
System.out.println(this.name); //小葵
System.out.println(Out.this.name); //小白
System.out.println(age); //5
}
}
Inn inn = new Inn();
inn.getName2();
}
public static void main(String[] args) {
Out out = new Out();
out.getName();
}
}
6.1.4 匿名内部类
- 没有类名的局部内部类(一切特征都与局部内部类相同)
- 必须继承一个父类或者实现一个接口
- 定义类,实现类,创建对象的语句合并,只能创建一个该类的对象
public class Out {
public static void main(String[] args) {
//匿名内部类
new UserService(){
@Override
public void add() {
System.out.println("添加了一个用户");
}
}.add();
}
}
6.2 Object类
6.2.1 getClass()方法
- public final Class<?> getClass(){ }
- 返回引用中存储的实际对象类型
- 应用:通常用来判断两个引用中实际存储对象类型是否一致
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("张三", 23);
//返回的是一个Class类型的对象
Class cl1 = s1.getClass();
System.out.println(cl1); //class commonClass.object.Student类的全限定类名
}
}
6.2.2 hashCode()方法
- 返回该对象的哈希码值
- 哈希值根据对象的地址或字符串或数字使用hash算法计算出来的int类型的数值
- 一般情况下,相同对象返回相同哈希码。
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("张三", 23);
//返回的是该对象的hashCode
System.out.println(s1.hashCode()); //460141958
Student s3 = s1;
System.out.println(s3.hashCode()); //460141958
}
}
6.2.3 toString()方法
- public String toString(){}
- 返回该对象的字符串表现形式
- 可以根据需求覆盖该方法
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("张三", 23);
//未重写toString方法 commonClass.object.Student@1b6d3586
System.out.println(s1.toString());
}
}
==================================================
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("张三", 23);
//重写toString方法 Student{name='张三', age=23}
System.out.println(s1.toString());
}
}
调用未重写的toString方法,返回的就是全限定名@哈希值,不过是十六进制表示形式
Object类中toString方法源码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
6.2.4 equals()方法
public boolean equals(Object obj){}
- 比较两个对象地址是否相同
- 可进行覆盖,比较两个对象内容是否相等
-覆盖步骤
- 比较两个引用是否指向同一个对象
- 判断obj是否为null
- 判断两个引用指向的实际对象类型是否一致
- 强制类型转换
- 依次比较各个属性值是否相等
@Override
//覆盖之后的equals方法只要两个对象的各个属性值都相等,那么就返回true
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
6.2.5 finalize()方法
- 当对象被判定为垃圾对象时,由JVM自动调用此方法,用以标记垃圾对象,进入回收队列
- 垃圾对象:没有有效引用指向此对象时,为垃圾对象
- 垃圾回收:由操作系统销毁垃圾对象,释放数据存储空间
- 自动回收机制:JVM内存耗尽,一次性回收所有垃圾对象
- 手动回收机制:使用System.gc();通知JVM执行垃圾回收
//1.重写finalize方法
@Override
protected void finalize() {
System.out.println(this.name +"被回收了");
}
=======================================================
//2.没有变量指向的对象在调用gc()方法的时候,会被回收
public class Demo1 extends Object{
public static void main(String[] args) {
new Student("张三", 23);
System.gc();
System.out.println("回收机制启动了");
}
}
/*
回收机制启动了
张三被回收了
*/
6.2.6 clone()方法
- protected Object clone();protected修饰的方法,只有当前类、与当前类在同一包中、子类可以使用该方法
public class Demo2 implements Cloneable{
public static void main(String[] args) throws CloneNotSupportedException {
//不可以使用clone方法
Student s1 = new Student();
//在当前类创建的对象可以使用clone方法
Demo2 demo2 = new Demo2();
demo2.clone();
}
}
只有实现了Cloneable的接口的类对象才能对克隆。Cloneable接口中没有任何方法,只是一个特征。
java中的克隆默认是浅克隆,只克隆基本属性,复合属性不能被克隆
浅克隆
public class People implements Cloneable{
private String name;
private Cat cat;
//重写clone方法,将protected改为public
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
=================================================================
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat("小白");
People p = new People("小新",c);
People cp1 = (People)p.clone();
//比较克隆前后两个cat对象的地址值
//是同一个对象
System.out.println(p.getCat().hashCode()); //460141958
System.out.println(cp1.getCat().hashCode()); //460141958
//克隆之后的两个people对象不是同一个
System.out.println(p.hashCode()); //1163157884
System.out.println(cp1.hashCode()); //1956725890
}
}
如果需要该类的复合类型也能够在该对象被克隆的时候克隆,则需要复合类型也实现Cloneable接口
深克隆
public class Cat implements Cloneable {
private String name;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
=========================================================================
public class People implements Cloneable{
private String name;
private Cat cat;
@Override
public Object clone() throws CloneNotSupportedException {
People p =(People)super.clone();
Cat c = (Cat)p.getCat().clone();
p.setCat(c);
return p;
}
}
========================================================================
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat("小白");
People p = new People("小新",c);
People cp1 = (People) p.clone();
//比较克隆前后两个cat对象也不是同一个
System.out.println(p.getCat().hashCode()); //460141958
System.out.println(cp1.getCat().hashCode()); //1163157884
//克隆之后的两个people对象不是同一个
System.out.println(p.hashCode()); //1956725890
System.out.println(cp1.hashCode()); //356573597
}
}
6.3 包装类
什么是包装类
- 基本数据类型对应的引用数据类型
- 基本数据类型是在栈内存中存放的,引用数据类型是在堆内存里面存放的
- Object类可以统一所有数据,包装类的默认值是null
类型转换,装箱与拆箱
- 装箱:将栈内存中的基本数据类型转换为堆内存中的引用数据类型(构造器或者静态valueOf方法)
- JDK1.5之后支持自动装箱和拆箱,本质还是使用的包装类的方法,不过是有JVM帮我们写过了代码。
public class Demo1 {
public static void main(String[] args) {
//JDK1.5之前不支持自动装箱和自动拆箱功能
int num = 10;
//装箱
Integer integer = new Integer(num);
Integer integer2 = Integer.valueOf(num);
//拆箱
int i = integer.intValue();
//JDK1.5之后支持自动装箱和拆箱功能
int num1 = 20;
//装箱
Integer integer1 = num1;
//拆箱
int i1 = integer1;
}
}
parseXXX()静态方法
可以实现字符串和基本数据类型之间的转换
public class Demo2 {
public static void main(String[] args) {
int i = 10;
//将基本类型转换为字符串
String str1 = i +"";
//后面的16,表示将该基本类型以几进制输出,10的16进制表示形式就是a
String s = Integer.toString(i, 16);
System.out.println(s); //a
//讲字符串转换为基本类型
String str2 = "180";
int i1 = Integer.parseInt(str2);
System.out.println(i1);
//将字符串的布尔类型转换为基本类型,"true"是true,非"true"就是false
String str3 = "true";
boolean b = Boolean.parseBoolean(str3);
System.out.println(b);
}
}
注意:需要保证类型兼容,否则会抛出NumberFormatException
异常
6.4 整数缓冲区
- Java预先创建了256个常用的整数包装类型对象
- 在实际应用当中,对已创建的对象进行复用。
面试题
public class Demo3 {
public static void main(String[] args) {
Integer integer1 = new Integer(100);
Integer integer2 = new Integer(100);
System.out.println(integer1==integer2); //false
Integer integer3 = 100;
Integer integer4 = 100;
System.out.println(integer3==integer4); //true
Integer integer5 = 200;
Integer integer6 = 200;
System.out.println(integer5==integer6); //false
}
}
通过反编译查看,发现自动装箱使用的是valueOf()方法
所以Integer integer3 = 100;等价于Integer integer3 = Integer.valueOf(100);
valueOf方法的源码,如果装箱的数在-128~127之间,使用的是缓冲区中的对象
如果超过这个范围,则是创建一个新的对象
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
===========================================================
static final int low = -128;
static final int high;
int h = 127;
high = h;
内存情况
6.5 String类
- 字符串是常量,创建之后不能改变(当改变字符串时,是重新开辟了一块新的空间)
- 字符串字面值存储在字符串常量池中,可以共享
- String类使用final修饰,不可变类
- String s = “Hello”; 产生一个对象,存储在字符串池中。
- String s = new String(“Hello”);产生两个对象,堆,池各存储一个。
public class Demo4 {
public static void main(String[] args) {
//创建两个对象,堆内存中一个,常量池中一个
String str1 = new String("张三");
String str2 = new String("张三");
String str3 = "张三";//在常量池中
System.out.println(str1==str2); //false
System.out.println(str1==str3); //false
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3));//true
}
}
常用方法
- int length():获取字符串长度
- char charAt(int index):根据下标获取字符
- boolean contains(String str):判断当前字符中是否包含str
- char[ ] toCharArray():将字符串转换为数组
- int indexOf(String str):查找str首次出现的下标,存在返回该下标,不存在,返回-1
- int lastIndexOf(String str):查找str最后出现的下标,存在返回该下标,不存在,返回-1
- String trim():去掉字符串前后的空格,中间的空格不会去掉
- String toUpperCase():将小写转换为大写 toLowerCase()转换为小写
- boolean endWith(String str):判断字符串是否以str结尾 startWith(str)判断是否以str开始
- String replace(char old, char new):使用新字符串替换旧字符串
- String[ ] split (String str):根据str做拆分参数为:“[ ,]+”使用多个空格,或者多个逗号拆分
- boolean equalsIgnoreCase(String str):忽略大小写比较
- int compareTo(String str):比较字符串在编码表中的大小
- String subString(int beginindex,int oldindex):根据开始和结束的角标截取字符串
“a”.compareTo(“b”); 返回值是-1,即a的编码值97-98
“a”.compareTo(“abcd”):返回值是-3,"abcd"的长度是4,"a"的长度是1,此时比较的是长度
String练习
public class Demo5 {
public static void main(String[] args) {
String str = "this is a text";
//将str中的单词单独提取出来
String[] strings = str.split(" ");
for (String string : strings) {
System.out.println(string);
}
//将text替换为practice
String replace = str.replace("text", "practice");
System.out.println(replace);
//在text前面插入一个easy
String replace1 = str.replace("text", "easy text");
System.out.println(replace1);
//将每个单词的首字母改为大写
for (int i = 0; i < strings.length; i++) {
char firstChar = strings[i].charAt(0);
//包装类Character中的方法
char newFirstChar = Character.toUpperCase(firstChar);
String newStr = newFirstChar + strings[i].substring(1);
System.out.println(newStr);
}
}
}
可变字符串
- StringBuffer:可变长字符串,JDK1.0提供,效率慢,线程安全
- StringBuilder:可变长字符串,JDK1.5提供,效率快,线程不安全(单线程使用)
StringBuffer
- append():追加
- insert():插入
- replace():替换
- delete():删除
- toString():转换为字符串
- reverse():反转
public class Demo6 {
public static void main(String[] args) {
StringBuffer str =new StringBuffer("努力就会成功") ;
str.append(",才怪");
System.out.println(str.toString()); //努力就会成功,才怪
str.insert(0,"只要");
System.out.println(str.toString());//只要努力就会成功,才怪
str.replace(8,11,",不可能的");//只要努力就会成功,不可能的
System.out.println(str.toString());
str.delete(0,2);//[ )左开右闭区间,包含左边,不包含右边
System.out.println(str.toString());//努力就会成功,不可能的
str.reverse();
System.out.println(str.toString());//的能可不,功成会就力努
}
}
StringBuilder
- 方法和用法和StringBuffer一样,效率比StringBuffer高,不过是线程不安全的。
6.6 BigDecimal类
- 很多实际应用需要精确计算,而double和float是近似值存储,不符合要求,所以需要使用BigDecimal类
public static void main(String[] args) {
float a = 0.4F;
float b = 0.5F;
System.out.println(b-a); //0.099999994
}
}
bigDecimal
- java.math包中
- 精确计算浮点数
- BigDecimal bd = new BigDecimal(“0.1”);
BigDecimal类中的加减乘除方法
public class Demo2 {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("0.5");
BigDecimal bd2 = new BigDecimal("0.4");
//减法
BigDecimal r1 = bd1.subtract(bd2);
System.out.println(r1);//0.1
//加法
BigDecimal r2 = bd1.add(bd2);
System.out.println(r2);//0.9
//乘法
BigDecimal r3 = bd1.multiply(bd2);
System.out.println(r3);//0.20
//除法,使用链式编程
BigDecimal r4 = new BigDecimal("0.9").divide(new BigDecimal("0.3"));
System.out.println(r4);//3
//如果除不尽需要告诉保留几位小数,并且使用什么方法保留
//除法第一个参数是被除数,第二个参数是保留几位数字
//第三个参数BigDecimal.ROUND_HALF_UP表示四舍五入保留
BigDecimal r5 = new BigDecimal("10.0")
.divide(new BigDecimal("3.0"), 5, BigDecimal.ROUND_HALF_UP);
System.out.println(r5);//3.33333
}
}
6.7 Date类
- Date表示特定的瞬间,精确到毫秒,Date类中的大部分方法都已经被Calendar类中的方法所取代。
- 时间单位
- 1秒=1000毫秒
- 1毫秒=1000微秒
- 1微秒=1000纳秒
public class Demo1 {
public static void main(String[] args) {
//两个构造方法
//new Date(long date);date:1970年1月1日00:00:00 GMT以来的毫秒数。
Date d1 = new Date();
System.out.println(d1.toLocaleString());//打印当地时间,方法已经过时 2020-8-3 21:40:20
Date d2 = new Date(d1.getTime()-(24*60*60*1000));
System.out.println(d2.toLocaleString());//打印昨天的时间 2020-8-2 21:40:20
System.out.println(d1.after(d2));//判断d1是不是在d2之后 true
System.out.println(d1.before(d2));//判断d1是不是在d2之前 false
//compare():如果参数Date等于此Date,则值为0 ;
// 如果此日期在Date参数之前,该值小于0 ; 如果此日期在Date参数0则值大于0 。
int i = d1.compareTo(d2);
System.out.println(i); // 1
}
}
6.8 Calendar类
- Calendar提供了获取或设置各种日历字段的方法
- 构造方法:protected Calendar();修饰符是保护型的,无法直接创建该对象
- 通过静态方法getInstance()创建一个Calendar对象
public class Demo1 {
public static void main(String[] args) {
//创建一个Calendar对象
Calendar calendar1 = Calendar.getInstance();
//获取年,月,日,时分秒
int year = calendar1.get(Calendar.YEAR);
int month = calendar1.get(Calendar.MONTH);//从0到11
int day = calendar1.get(Calendar.DAY_OF_MONTH);
int hour = calendar1.get(Calendar.HOUR_OF_DAY);
int minute = calendar1.get(Calendar.MINUTE);
int second = calendar1.get(Calendar.SECOND);
System.out.println(year+"年"+(month+1)+"月"+day+"日"+hour+":"+minute+":"+second);//2020年8月4日10:34:34
//设置年份
calendar1.set(Calendar.YEAR,2019);
System.out.println(calendar1.getTime().toLocaleString());//2019-8-4 10:36:43
//获取该日历对象的毫秒值
long timeInMillis = calendar1.getTimeInMillis();
System.out.println(timeInMillis);//1564886361058
//add方法,给指定的常量增加指定的数量
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR,2);
System.out.println(calendar.getTime().toLocaleString());//2022-8-4 10:41:12
//可以获取每个月的最大天数和最小天数,8月共用31天,所有最大值是31,最小值是1
int maximum = calendar.getMaximum(Calendar.DAY_OF_MONTH);
int minimum = calendar.getMinimum(Calendar.DAY_OF_MONTH);
System.out.println(maximum);//31
System.out.println(minimum);//1
}
}
6.9 SimpleDateFormat类
- SimpleDateFormat是一个以与语言环境有关的方式来格式化和解析日期的具体类
- 实现指定格式的字符串和日期对象的转换
- 进行格式化:(日期-文本),解析:(文本-日期)
- 常用的时间模式字母
天:h(表示12进制的)H:(24进制的)
public class Demo1 {
public static void main(String[] args) throws ParseException {
//先创建一个简单日期格式化对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//创建一个日期对象
Calendar c = Calendar.getInstance();
Date time = c.getTime();
//格式化日期
String s = sdf.format(time);
System.out.println(s);//2020/08/04 10:55:29
//解析字符串
Date date = sdf.parse("2010/07/04 10:55:29");
System.out.println(date);//Sun Jul 04 10:55:29 CST 2010
}
}
6.10 System类
- System系统类,主要用于获取系统的属性数据和其他操作,构造方法是私有的
- System.arrayCopy和Arrays.arrayCopy区别:Arrays.arrayCopy内部使用的就是System.arrayCopy方法。System.arrayCopy是native修饰的,效率高,建议使用System.arrayCopy来复制数组
public class Demo1 {
public static void main(String[] args) {
int[] oldarray = {1,2,3,4,5,6,7,8};
int[] newarray = new int[8];
//复制数组 arraycopy()
/*
* src:源数组
* srcPos:从源数组哪个位置开始复制 0
* dest:目标数组
* destPos:复制到目标数组哪个位置
* length:复制的长度
* */
System.arraycopy(oldarray,0,newarray,0,6);
System.out.println(Arrays.toString(newarray));//[1, 2, 3, 4, 5, 6, 0, 0]
//currentTimeMillis()获取当前系统时间,返回的是毫秒值
long start = System.currentTimeMillis();
for (int i = 0; i < 88888; i++) {
System.out.print(i);
}
System.out.println("/n");
long end = System.currentTimeMillis();
System.out.println("循环共用时:"+(end-start));//循环共用时:227
//exit表示退出JVM,程序运行结束
System.exit(0);
System.out.println("程序退出了..."); //不会被执行
}
}
6.11 File类
- 代表物理盘符中的一个文件或者文件夹
File.pathSeparator //路径分隔符:;
File.separator //名称分隔符:\
文件操作
public class Demo1 {
public static void main(String[] args) throws Exception{
//1.创建文件
File file = new File("d:\\file.txt");
if(!(file.exists())){ //如果文件不存在,则创建该文件
System.out.println(file.createNewFile()); //true,表示创建成功
}
//2.删除文件
System.out.println(file.delete());
//虚拟机退出的时候自动删除
file.deleteOnExit();
//3.获取文件信息
System.out.println("文件的绝对路径"+file.getAbsolutePath()); //d:\file.txt
System.out.println("文件绝对路径或者相对路径:"+file.getPath()); //d:\file.txt
System.out.println("文件的名称"+file.getName()); //file.txt
System.out.println("文件的父目录"+file.getParent()); //d:\
System.out.println("文件的大小"+file.length());//10,单位(字节)
System.out.println("文件的创始时间"+
new Date(file.lastModified()).toLocaleString()); //2020-8-6 14:18:10
//4.判断
System.out.println("文件是否可写"+file.canWrite()); //true
System.out.println("是否是文件"+file.isFile()); //true
System.out.println("文件是否隐藏"+file.isHidden());//false
}
}
文件夹操作
public class Demo1 {
public static void main(String[] args) {
//创建文件夹
File dir = new File("e:\\aa\\bb\\cc");
if(!dir.exists()){
//dir.mkdir();只能创建单级目录
System.out.println(dir.mkdirs()); //true
}
//删除文件夹
/*dir.delete(); //只删除最后一级目录,且该目录必须是空目录
dir.deleteOnExit();*/
//文件夹操作
System.out.println("文件夹绝对路径"+dir.getAbsolutePath());//e:\aa\bb\cc
System.out.println("文件夹路径"+dir.getPath());//:\aa\bb\cc
System.out.println("文件夹的名称"+dir.getName());//cc
System.out.println("父目录"+dir.getParent());//e:\aa\bb
System.out.println("文件夹大小"+dir.length()); //0
System.out.println("创建:"+new Date(dir.lastModified()).toLocaleString());
//创建时间2020-8-6 14:30:26
//判断
System.out.println("是否是文件夹"+dir.isDirectory());
System.out.println("是否是隐藏的"+dir.isHidden());
//遍历文件夹
String[] list = dir.list(); //获取文件夹中的所有文件及文件夹,结果是字符串数组
File[] files = dir.listFiles();//获取文件夹中的所有文件及文件夹,结果是文件数组
}
}
6.12 FileFilter接口
public interface FileFilter
boolean accept(File pathname)
- 当调用File类中的listFiles()方法时,支持传入FileFilter接口接口实现类,对获取文件进行过滤,只有满足条件的文件才可出现在返回值中。
//只有后缀是jpg的文件才会在返回数组中
dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.getName().endsWith(".jpg")){
return true;
}
return false;
}
});
使用递归遍历一个目录下的所有文件夹
public class Demo2 {
public static void main(String[] args) {
File file = new File("d:\\文档");
getFiles(file);
}
public static void getFiles(File dir){
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
if(files[i].isDirectory()){
getFiles(files[i]);
}else {
System.out.println(files[i].getAbsolutePath());
}
}
}
}
使用递归删除一个目录下的所有文件夹及文件
public class Demo3 {
public static void main(String[] args) {
deleteDir(new File("d:\\新建文件夹"));
}
public static void deleteDir(File dir){
File[] files = dir.listFiles();
for (File file : files) {
if(file.isDirectory()){
deleteDir(file);
}else {
file.delete();
}
}
dir.delete();
}
}
6.13 Properties集合
1. 存储属性名和属性值
2. 属性名和属性值都是字符串类型
3. 没有泛型
4. 和流有关
public class Demo1 {
public static void main(String[] args) throws IOException {
//1.创建集合
Properties pp = new Properties();
//2.添加元素
pp.put("aaa","aaa");
pp.put("bbb","bbb");
pp.put("ccc","ccc");
//3.遍历集合 entrySet keySet,stringPropertyNames...
Set<String> set = pp.stringPropertyNames();
for (String s : set) {
System.out.println(s+"===="+pp.getProperty(s));
}
//4.和流有关的方法,list,将集合中的内容打印到硬盘中
PrintWriter pw = new PrintWriter("d:\\print.txt");
pp.list(pw);
pw.close();
//store保存方法
pp.store(new FileOutputStream("d:\\print.properties"),"注释");
//load加载方法
pp.load(new FileInputStream("d:\\print.properties"));
System.out.println(pp); //{bbb=bbb, aaa=aaa, ccc=ccc}
}
}
Java网络编程
7.1 IP地址
参考博客:计算机网络基础
ip地址类:java.net.InetAddress
- 用来唯一定位一台网络上的计算机
- 127.0.0.1:用于本机软件环回测试的 IPV4地址
7.1.1 IP地址分类
- IPv4/IPv6
ipv4:127.0.0.1由四个字节组成,共有大概42亿个
ipv6 : fe80::e53b:414d:b973:477c由8个字节组成 - 公网和私网
公网(互联网)
私网(局域网):192.168.xx.xx(有三个)
7.2.2 IP地址相关
InetAddress类
public class Demo1 {
public static void main(String[] args) throws UnknownHostException {
//获取本机ip
///127.0.0.1
InetAddress address1 = InetAddress.getByName("127.0.0.1");
//localhost/127.0.0.1
InetAddress address2 = InetAddress.getByName("localhost");
//DESKTOP-2351F3T/192.168.121.4
InetAddress address3 = InetAddress.getLocalHost();
//www.baidu.com/220.181.38.150
InetAddress address4 = InetAddress.getByName("www.baidu.com");
}
}
InetSocketAddress类
public class Demo2 {
public static void main(String[] args) {
InetSocketAddress isa = new InetSocketAddress("127.0.0.1",80);
System.out.println(isa.getAddress());
System.out.println(isa.getHostName());
System.out.println(isa.getPort());
}
}
7.2 端口
7.2.1 什么是端口
端口表示计算机中的一个程序的进程
- 不同的进程有不同的端口号,用来区分软件
- 0-65535
- TCP,UDP:实际有65536*2个,单个协议下,端口号不能冲突
7.2.2 端口分类
公有端口0-1023,尽量不要占用
- HTTP:80
- HTTPS:443
- FTP:21
- Telent:23
程序注册端口1024-49151,分配给用户程序的
- Tomcat:8080
- Mysql:3306
- Oracle:1521
动态、私有端口49152-65535
7.2.3 端口有关的dos命令
netstat -ano #查看所有的端口
netstat -ano|findstr "3306" #查看指定的端口
tasklist|findstr "8696" #查看指定端口的进程
7.3 通信协议
- TCP:用户传输协议
- UDP:用户数据报协议
- IP:网络互连协议
7.3.1 TCP和UDP
TCP:打电话
- 连接,文档
三次握手,四次挥手
最少需要三次,保持稳定连接
A:在不在
B:在
A:在的话那我就要发数据了
A:我要断开连接了
B:你要断开了吗
B:你真的要断开了吗
A:我真的要断开了
- 客户端、服务端
- 传输完成,释放连接,效率低
UDP:发短信
- 不连接,不稳定
- 客户端、服务端,没有明确的界限
- 不管有没有准备好,都可以发给你
TCP
客户端
- 1.连接服务器 ,创建socket连接
- 2.发送消息
服务端
- 创建自己的端口ServerSocket
- 等待对方连接,通过accept()方法获取一个链接
- 接收客户端的消息
- socket.shutdownOutput():通知服务器,此次传输已经完毕
UDP
不需要连接,但是需要知道对方的地址
7.4 URL
使用url对象下载网上的资源
public class Demo1 {
public static void main(String[] args) throws Exception{
URL url = new URL("https://xxx.xxx.xxx/xxx");
//连接到这个资源
URLConnection urlConnection = url.openConnection();
InputStream is = urlConnection.getInputStream();
//创建一个输出流来接受该输入流
FileOutputStream fos = new FileOutputStream("a.m4a");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
is.close();
}
}
I/O流
8.1 什么是流
- 概念:内存与存储设备之间存储数据的通道。
- 数据借助流来进行传输
IO流
I:Input输入(读取)输入:把硬盘中的数据,读取到内存中使用
O:Output输出(写入)输出:把内存中的数据,写入到硬盘中保存
流:
- 数据(字符,字节)
- 1个字符=2个字节
- 1个字节=8个二进制位
字节流:
(使用字节流读取中文会出现乱码,因为根据编码不同,一个中文占2个或者3个字节,可能读取不全一个中文的全部字节)
- 字节输入流:InputStream
- 字节输出流:OutputStream
字符流:
- 字符输入流:Reader
- 字符输出流:Writer
8.2 流的分类
按方向分
(都是以内存为参照物的)
- 输入流:将存储设备中的内容读取到内存中。(Input)
- 输出流:将内存中的内容写入到存储设备中。(Output)
按单位分
- 字节流:以字节为单位,可以读取所有数据
- 字符流:以字符为单位,只能读写文本数据
按功能分
- 节点流:具有实际传输数据的读写功能
- 过滤流:在节点流的基础上增加功能
8.3 字节流
字节流的父类(抽象类)
public abstract class InputStream
extends Object
implements Closeable
8.3.1 字节输入输出流
InputStream
- abstract int read() 从输入流读取数据的下一个字节。
- int read(byte [ ] b) 从输入流读取一些字节数,并将它们存储到缓冲区 b 。
- int read(byte[ ] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组。
OutputStream
- void write(byte[ ] b) 将 b.length字节从指定的字节数组写入此输出流。
- void write(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。
- abstract void write(int b) 将指定的字节写入此输出流。
8.3.2 文件输入输出流
FileInputStream
【文件输入流】
- 构造方法 FileInputStream(File file) 参数file:file对象
- 构造方法 FileInputStream(String name) 参数name:文件所在的绝对路径
- public int read(byte[ ] b)
读取文件的字节流,从流中读取多个字节,将读的内容存入到数组中,返回实际独到的字节数,如果达到文件的末尾,返回-1
FileOutputStream
【文件输出流】
- public void write(byte[ ] b) 一次写入多个字节,将b数组中的所有字节,写入到输入流。
文件输入输出流的简单使用
//文件读取(输入)
public class Demo1 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("d:\\a.txt");
int len = 0;
byte[] bytes= new byte[1024];
while((len=fis.read(bytes)) != -1){
System.out.print(new String(bytes,0,len));
}
fis.close();
}
}
=============================================================
//内容写入(输出)
public class Demo2 {
public static void main(String[] args) throws IOException {
//第二个参数true表示继续在后面追加写入内容,而不是覆盖
FileOutputStream fos = new FileOutputStream("e:\\a.txt",true);
String str = "helloWorld";
fos.write(str.getBytes());
fos.close();
}
}
使用文件输入输出流实现文件的复制
public class Demo3 {
public static void main(String[] args) throws IOException {
//实现文件的复制与粘贴
//统计时间
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:\\9.png");
FileOutputStream fos = new FileOutputStream("E:\\9.jpg");
byte[] bytes = new byte[1024];
int len = 0;
while ((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
long end = System.currentTimeMillis();
System.out.println("复制粘贴共耗时"+(end-start)+"毫秒");
}
}
8.4 字节缓冲流
- 缓冲流BufferedInputStream/BufferedOutputStream
- 提高IO效率,减少访问磁盘的次数
- 数据存储在缓冲器中,flush方法是将缓冲区的内容刷新进文件中。直接close也会执行flush方法。
BufferedInputStream
- 内置缓冲区,不用自己手动添加缓冲区
public class Demo4 {
public static void main(String[] args) throws IOException {
//构造方法需要传入一个字节输入流,对其进行增强
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("d:\\a.txt"));
int len = 0;
while((len = bis.read())!=-1){
System.out.println((char)len);
}
bis.close();
}
}
BufferedOutputStream
- 默认缓冲区8k大小,写入的数据不超过8k,数据都在缓冲区中存放,需要关闭流或者刷新,才能将缓冲区中的数据写入到文件。
public class Demo5 {
public static void main(String[] args) throws IOException {
//使用字节缓冲输出流写一个文件
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("e:\\c.txt"));
bos.write("你好,世界".getBytes());
bos.close();
}
}
8.5 对象流
ObjectInputStream
ObjectOutputStream
- 增强了缓冲区功能
- 增强了读写8中基本数据类型和字符串功能
- 增强了读写对象的功能 readObject( ):从流中读取一个对象
- writeObject( ):向流中写入一个对象
8.6 序列化和反序列化
使用流传输对象的过程称为序列化和反序列化。
- 需要被序列化的对象的类必须要实现Serializable接口(起标记作用)
8.6.1 序列化
- 将内存中的对象写入到文件中
public class Demo1 {
public static void main(String[] args) throws IOException {
//序列化,将内存中的对象写入到文件中
Person p = new Person("广志",32);
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("e:\\person.txt"));
oos.writeObject(p);
oos.close();
}
}
8.6.2 反序列化
- 将文件中的对象读取到内存中
public class Demo2 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("e:\\person.txt"));
Object o = ois.readObject();
System.out.println(o); //io.objectInput.Person@58372a00
}
}
注意
InvalidClassException(无效类异常)
- 序列化类必须要实现serizable接口
- 序列化类中的对象属性的类也要实现serizable接口
- serialVersionUID:序列化版本号ID,保证序列化和反序列化是同一个类
- 使用transient(瞬间的)修饰属性,该属性就不会被序列化
- 静态属性不能被序列化
- 序列化多个对象,可以借助集合来实现,序列化方法传入集合对象。
8.6.3 编码方式
字符编码
- ISO-8859-1 收录除ASCII外,还包括西欧,希腊语,泰语,阿拉伯语,希伯来语对应的文字符合。(一个字节)
- UTF-8 针对Unicode码表的可变长度字符编码(一个,两个,或者三个字节)
- GB2312简体中文
- GBK简体中文,扩充(ANSI在中国就表示GBK)
- BIG5繁体中文
当编码方式和解码方式不一致,会出现乱码
8.7 字符流
8.7.1 字符输入输出流
Reader
【字符输入流】
- java.io.Reader:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类。
共性的成员方法:
- int read() 读取单个字符
- int read(char[] cbuf) 一次读取多个字符,将字符读入数组。
- void close() 关闭该流并释放与之关联的所有资源。
Writer
【字符输出流】
- java.io.Writer:写入字符流的抽象类,它定义了字节输出流的基本共性功能方法。
- void write(int c) 写入单个字符。
- void write(char[] cbuf) 写入字符数组。
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off:数组的开始索引,len:写的字符个数。
- void write(String str) 写入字符串。
- void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
- void flush() 刷新该流的缓冲。
- void close() 关闭此流,但要先刷新它。
8.7.2 文件字符输入输出流
FileReader
【文件字符输入流】
- FileReader:文件字符输入流
- 作用:把硬盘文件中的数据以字符的方式读取到内存中
public class Demo1 {
public static void main(String[] args) throws Exception{
FileReader fr = new FileReader("d:\\a.txt");
char[] chars = new char[1024];
int len = 0;
while ((len = fr.read(chars))!= -1){
System.out.println(chars);
}
fr.close();
}
}
FileWriter
【文件字符输出流】
- FileWriter:文件字符输出流
- 作用:把内存中字符数据写入到文件中
/*写换行:写换行符号
windows:\r\n
linux:/n
mac:/r
*/
public class Demo1 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("e:\\d.txt");
for (int i = 0; i < 20; i++) {
fw.write("你好\r\n");
}
fw.flush();
fw.close();
}
}
使用字符输入输出流完成文件的复制
- 只能复制文本文件,不能复制图片或者二进制图片
public class Demo2 {
public static void main(String[] args) throws Exception{
//使用字符输入输出流完成文件的复制
FileReader fr = new FileReader("e:\\d.txt");
FileWriter fw = new FileWriter("e:\\ddd.txt");
char[] chars = new char[1024];
int len = 0;
while ((len = fr.read(chars))!= -1){
fw.write(chars,0,len);
}
fw.close();
fr.close();
}
}
8.8 字符缓冲流
BufferReader/BufferWriter
- 高效读写,支持输入换行符
- 可一次写一行,读一行
public class Demo1 {
public static void main(String[] args) throws Exception{
//带有缓冲区的字符输入流
BufferedReader br = new BufferedReader(new FileReader("e:\\d.txt"));
//第一种方式,自定义缓冲区
char[] chars = new char[1024];
int len = 0;
while ((len = br.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
//第二种方式,每次读取一行
String line = null;
while ((line = br.readLine())!= null){
System.out.println(line);
}
br.close();
}
}
============================================================================
public class Demo1 {
public static void main(String[] args) throws Exception{
//字符缓冲输出流写数据
BufferedWriter bw = new BufferedWriter(new FileWriter("e:\\dd.txt"));
for (int i = 0; i < 10; i++) {
bw.write("你好JAVA");
bw.newLine();//换行符,不同操作系统换行符不同,便于扩展
}
bw.close();
}
}
8.9 打印流
printWriter
- 封装了print()/println()方法,支持写入后换行
- 支持数据原样打印,
public class Demo1 {
public static void main(String[] args) throws Exception{
PrintWriter pw = new PrintWriter("d:\\c.txt");
pw.println(97);
pw.println("你好");
pw.println('a');
pw.close();
}
}
8.10 转换流
桥接转换:InputStreamReader/OutputStreamWriter
- 是字节流(硬盘)通向字符流的桥梁(内存)
- 使用指定的charset读取字节并将其解码成字符
- 可将字节流转换为字符流
- 可设置字符的编码方式
InputStreamReader
public class Demo1 {
public static void main(String[] args) throws Exception{
InputStreamReader isr = new InputStreamReader(
new FileInputStream("e:\\d.txt"),"utf-8");
//一次读取一个字节
int len = 0;
while ((len=isr.read())!=-1){
System.out.print((char)len);
}
isr.close();
}
}
OutputStreamWriter
public class Demo2 {
public static void main(String[] args) throws Exception{
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("e:\\aa.txt"),"gbk");
osw.write("Hello,你好");
osw.close();
}
}