java学习 asleer 2022/6/27
想成为大佬,就是要通过学习和积累,提升自己的各方面能力,比较抽象,共勉!
团队协作能力
高效沟通能力
需求分析能力
架构设计能力
抽象复用能力
独立创造能力
问题解决能力
归纳总结能力
自主学习能力
工具利用能力
高效编码能力
信息检索能力
开源建设能力
源码阅读能力
自测审查能力
文档编写能力
知识表达能力
绘图描述能力
兴趣驱动(长期学习能力)
保持好奇心
其他
视频
文档
老师忠告
JAVA_HOME
= 指向jdk安装的主目录%JAVA_HOME%\bin
.class
字节码文件.class
加载到jvm运行在命令台使用table可以实现命令补全
中文符号不需要转义,英文符号需要
介绍:
用于注解说明解释程序的文字,提高了代码的可阅读性,是良好的编程习惯。将自己的思想通过注释先整理出来,再用代码体现。对自己写的代码负责。
类型:
单行注释://后跟注释,快捷键:ctrl+/
多行注释:/* 注释 */,快捷键:ctrl+shift+/
多行注释里不能nest多行注释
文档注释:注释内容可以被javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档,一般写在类
/**
* @author asleer
* @version 1.0
*/
javadoc -d 文件夹名 -xx -yy 文件名.java
shift+tab
整体向左边移动
dir:查看当前目录有什么内容
cd:切换到其他盘,切换到c盘:cd /D c:
切换到当前盘的其他目录,看上图所示,两种写法
cd ..:切换到上一级
cd \:切换到根目录
tree:查看指定目录下的自己目录
cls:清屏
退出dos exit
了解内容:
echo ok > hello.txt
主要用于Linux
变量是程序的基本组成单位
概念:
类型+名称+值
for example:
int a = 1;
计算机分配一个内存存储数值1,a是访问这个内存的方式(地址)
+
号的使用基本数据类型
数值型
字符型(char[2]):存放单个字符“a”或汉字
布尔型(boolean[1]):存放true,false
引用数据类型
byte[字节]:-128 ~ 127
short[短整型]:-(2^15) ~ 2^15-1等价于-32768 ~32767
int[整型]:-2^31 ~ 2^31-1
long[长整型]:-2^63 ~ 2^63-1
使用细节
l
或 L
float[单精度]:-3.403E38 ~ 3.403E38
double[双精度]:-1.798E308 ~ 1.798E308
说明一下
使用细节
java的浮点型常量默认为double型,可以但不必要在后加 D
或 d
,声明float型常量,需后加 f
或 F
浮点型常量的两种表示形式
通常情况下,应该使用double类型,因为他比float精度更高
浮点数使用陷阱:2.7和8.1/3比较
当我们对运算结果是小数的进行相等判断要小心。应该以两个数的差值的绝对值,在某个精度范围内判断
xxxxxxxxxx
public class floatdetail{
public static void main(String[] args){
double num1 = 2.7;
double num2 = 8.1 / 3;
System.out.println(num1);//2.7
System.out.println(num1);//2.666666669
if( num1 == num2 ){
System.out.println("num1 == num2,equal");
}
System.out.println(Math.abs(num1 - num2));
if(Math.abs(num1 - num2) < 0.000001){
System.out.println("差值非常小,认为相等");
}
}
}
char:表示单个字符,可以存放汉字,多个字符使用字符串String
使用细节:
\n
表示单个字符(int)
字符System.out.println('a'+10)
= 107本质:
存储:‘a’=>码值97=>二进制(1100001)=>存储
字符和码值的对应关系是通过字符编码表决定的
介绍:boolean类型数据只允许取值true和false,占用1个字节。不能用0或非0的整数替代。
精度小的类型自动转换为精度大的类型。
数据类型按精度大小排序:
char => int => long => float => double
byte => short => int => long => float => double
自动类型转换注意和细节
自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型,使用时要加上强制转换符( )
,但可能造成精度降低或外溢,格外要注意。
基本数据类型 => String:n + ""
String => 基本数据类型:
通过基本类型的包装类调用parseXX方法即可
使用基本数据类型对应的包装类的相应方法,得到基本数据类型
xfor example: String num = "123";
int num = Integer.parseInt(num);//得到数字123
System.out.println(num.charAt(0));//得到字符'1'
xxxxxxxxxx
Integer.parseInt("");
Double.parseDouble("");
Float.parseFloat("");
Short.parseShort("");
Long.parseLong("");
Boolean.parseBoolean("");
Byte.parseByte("");
xxxxxxxxxx
String yyy = "xxx";
"xxx".equals(yyy)//则为true
中文在线文档:Java 8 中文版 - 在线API中文手册 - 码工具 (matools.com)
使用方式:
% 取模的本质:a % b = a - a / b * b
,如果a是小数,则 a % b = a - (int)a / b * b
自增++:
独立使用时:i++; == ++i; == i = i +1;
作为表达式使用:
++i
先自增后赋值i++
先赋值后自增结果都是boolean,注意不要把 ==
写成 =
基本赋值运算符:int a = 10;
复合赋值运算符:
a += b;
[等价于a = a + b
]a -= b;
[等价于a = a - b
]*=, /=, %=
特点
运算顺序从右往左
复合赋值运算符会进行类型转换
xxxxxxxxxx
byte b = 3;
b += 2;//等价于 b = (byte)(b + 2);
b++;// b = (byte)(b + 1);
xxxxxxxxxx
a > b ? a : b;
true?new Integer(1):new Double(2.0);1.0//因为三元运算符是一个整体,后者提高了前者的运算级别
运算规则:
使用细节:
if--else
语句概念:对各种变量、方法和类等命名时使用的字符序列,或凡是自己可以起名字的地方
规则:
规范:
二进制:binary
四种表示方式:
转十进制:从最低位(右边)开始,将每个位上的数提取出来,乘以2/8/16的(位数-1)次方,然后求和
十进制转其他:将该数不断除以2/8/16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
34转成二进制 = 0B00100010,0B后面自动补成八位
二进制转其他:
转二进制:
对于有符号的而言:
~2 = ?
2的原码等于补码:00000000 00000000 00000000 00000010
~2的补码:11111111 11111111 11111111 11111101
~2的反码:11111111 11111111 11111111 11111100
~2的原码:10000000 00000000 00000000 00000011
所以答案为-3
&
:两位全为1,结果为1,否则为0|
:两位有一个为1,结果为1,否则为0^
:两位一个为0,一个为1,结果为1,否则为0~
:0->1,1->0>>
:低位溢出,符号位不变,并用符号位补溢出的高位。本质是除以移动位数的2<<
:符号位不变,低位补0。本质是乘以移动位数的2>>>
:也叫无符号右移,运算规则是:低位溢出,高位补0 程序按顺序从上到下进行执行
xxxxxxxxxx
if(条件表达式1){
执行代码块1;//可以有多条语句
}
else if(条件表达式2){
执行代码块2;
}
......
else{
执行代码块n;
}
switch和if的比较
- 如果判断的具体数值不多,而且符合六种数据(byte,short,int,char,enum[枚举],String)类型,虽然两个语句都可以,但是建议使用switch语句
- 其他情况:对区间判断,对结果为boolean类型判断,使用if,if的使用范围更广
xxxxxxxxxx
switch(表达式){
case 常量1://当表达式返回值对于常量1时执行
语句块1;
break;//表示退出switch,而不是退出程序。如果没有break,---> 则不经过判断,直接执行语句块2(穿透) <---
case 常量2:
语句块2;
break;
......
default:
default语句块;
break;//这个break可有可无
}
需要注意的细节:
xxxxxxxxxx
for(循环变量初始化;循环条件;循环变量迭代){
循环操作(语句);
}
使用细节:
xxxxxxxxxx
while(循环条件){
循环体(语句);
循环变量迭代;
}
xxxxxxxxxx
do{
循环体(语句);
循环变量迭代;
}while(循环条件);
;
用法:用于终止某个语句块的执行,等于跳出本次循环,一般使用在switch或者循环中。
用法:用于结束本次循环,继续执行下一次循环
用法:使用在方法,表示跳出所在的方法;如果return写在main方法,退出程序
动态初始化(类似创建变量的两种方式)
xxxxxxxxxx
数据类型 数组名[] = new 数据类型[];
数据类型[] 数组名 = new 数据类型[];
int[] a = new int[5];
xxxxxxxxxx
数据类型[] 数组名;
int[] a;
数组名 = new 数据类型[大小];
a = new int[10];
静态初始化
要求知道数组有多少元素,具体指
xxxxxxxxxx
double[] hens = {3, 5, 1, 3.4, 2, 50}
hens.length//可以得到数组的长度
String strs[] = new String[]{"a", "b", "c"};//ok
String[] strs = new String[3]{"a", "b", "c"};//error,编译不通过
数组内数据赋值同样遵守自动类型转换
数组中的元素可以使任何数据类型,包括基本数据类型和引用数据类型,但不能混用
数组创建后,如果没有赋值,有默认值:
数组的下标从0开始
数组的下标必须在指定范围内使用,否则报:下标越界异常
xxxxxxxxxx
int[] arr1 = {1, 2, 3};
//地址传递
int[] arr2 = arr1;
//值传递
int[] arr2 = new int[arr1.length];
for(int i = 0; i < arr1.length; i++){
arr2[i] = arr[1];
}
xxxxxxxxxx
int[] arr = {1, 2, 3};
int[] arrNew = new int[arr.length +1 ];
//选择创建一个新的数组来存放新的元素
for(int i = 0; i < arr.length; i++){
arrNew[i] = arr[i];
}
arrNew[arrNew.length - 1] = 4;
arr = arrNew;
xxxxxxxxxx
import java.util.Scanner;
public class arrayadd{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int[] arr = {1, 2, 3};
do {
int[] arrNew = new int[arr.length + 1];
for(int i = 0; i < arr.length; i++){
arrNew[i] = arr[i];
}
System.out.println("请添加数值");
int addnum = scanner.nextInt();
arrNew[arrNew.length - 1] = addnum;
arr = arrNew;
System.out.println("扩容后数组为");
for(int i = 0; i < arrNew.length; i++){
System.out.print(arrNew[i] + "\t");
}
System.out.println();
System.out.println("添加成功,是否继续?y/n");
char key = scanner.next().charAt(0);
if(key == 'n')
break;
}while(true);
System.out.println("退出程序~~");
}
}
xxxxxxxxxx
import java.util.Scanner;
public class arrayReduce{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int[] arr = {1, 2, 3, 4, 5};
int temp;
do{
int[] arrNew = new int[arr.length - 1];
System.out.println("请选择你要删除的元素");
int reducenum = scanner.nextInt();
for(int i = 0; i < arr.length; i++){
if(arr[i] == reducenum){
for(int j = i; j < arr.length - 1; j++){
arrNew[j] = arr[j + 1];
}
break;
}else{
arrNew[i] = arr[i];
}
}
arr = arrNew;
System.out.println("缩减后的数组为");
for(int i = 0; i < arrNew.length; i++){
System.out.print(arrNew[i] + "\t");
}
System.out.println();
if(arrNew.length == 1){
System.out.println("当前只剩最后一个元素,不能再缩减");
break;
}
System.out.println("是否继续缩减?y/n");
char addnum = scanner.next().charAt(0);
if(addnum == 'n')
break;
}while(true);
System.out.println("退出程序~~");
}
}
将多个数据依指定的顺序进行排列的过程
思想:通过对待排序序列从后向前,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底下的气泡一样逐渐向上冒
xxxxxxxxxx
public class BubbleSort{
public static void main(String[] args) {
int[] arr = {22, 55, 44, 77, 99, 33, 11};
int temp;
for(int i = 0; i < arr.length - 1; i++){
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j + 1]){
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
常用的查找:
dimensional:维数
xxxxxxxxxx
int[][] y
int[] y[]
int y[][]
xxxxxxxxxx
//列数相等
int[][] arr = new int[5][6];
//列数不确定形
//1
//2 2
//3 3 3
int[][] arr = new int[3][];//创建了3个一维数组,但每个元素没有开空间
for(int i = 0; i < arr.length; i++){
arr[i] = new int[i + 1];//给元素开空间
for(int j = 0; j < arr[i].length; j++){
arr[i][j] = i + 1;
}
}
把所有猫的特性(属性)提取出来 ===>
猫类Cat:自定义的数据类型,包含:
===> 猫对象:具体的一只猫,而不再是一个种类
xxxxxxxxxx
public class xxx{
public static void main(String[] args){
//cat是对象名(对象引用)
//new Cat()创建的对象空间(数据)才是真正的对象
Cat cat = new Cat();
cat.name = "xioabai";
cat.age = 3;
cat.color = "white";
}
}
class Cat{
String name;
int age;
String color;
}
栈里的cat是对象的名字,堆里空间与数据才是真正的对象
java内存的结构分析
java创建对象的流程简单分析
属性(成员变量/field即字段)
属性的定义语法同变量
属性如果不赋值,有默认值,规则和数组一致。
类似于数组
简单介绍:某些情况下,我们需要定义方法。比如人类,除了属性外,还有一些行为,这时候需要使用成员方法。
作用/好处:
xxxxxxxxxx
//main里面写
//先创建对象
//再调用输出
Person p = new Person();
p.speak();
int returnRes = p.getSum(10, 20);
class Person{
String name;
int age;
//方法(成员方法)
//public表示方法是公开的
//void表示方法没有返回值
//speak():speak是方法名,()是形参列表
//{}是方法体,里面写需要执行的代码
public void speak(){
System.out.println("我是一个好人");
}
public int getSum(int num1, int num2){
int res = num1 + num2;
return res;
}
}
xxxxxxxxxx
public(访问修饰符) void/返回数据类型 方法名 (形参列表){
//方法体
语句;
return 返回值;
}
使用细节:
形参列表的细节:
printArr(int[][] map)
,或对象 Person p
方法调用细节:
parameter:参数
基本数据的传参机制
引用数据的传参机制
递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于解决复杂的问题,同时让代码变得更简洁
xxxxxxxxxx
//打印问题,输出2,3,4
public void recursion(int n){
if(n > 2){
recusion(n - 1);
}
System.out.println("n=" + n);
}
//阶乘问题
public int factorial(int n){
if(n == 1){
return 1;
}else{
return factorial(n - 1) * n;
}
}
递归的重要规则
xxxxxxxxxx
public class MiGong{
public static void main(String[] args){
int[][] map = new int[8][7];
for(int i = 0; i < 7; i++){
map[0][i] = 1;
map[7][i] = 1;
}
for(int i = 0; i < 8; i++){
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
map[2][2] = 1;//导致回溯
System.out.println("=====当前地图=====");
for(int i = 0; i < map.length; i++){
for(int j =0; j < map[i].length; j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
T ti = new T();
ti.findWay(map, 1, 1);
System.out.println("=====新地图=====");
for(int i = 0; i < map.length; i++){
for(int j =0; j < map[i].length; j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
class T{
//找到就返回true,否则就返回false
//i和j表示老鼠当前的坐标
//规定,0是可走的路,1是障碍物,2是走过的确认可走的路,3是走过的确认不可走的路
//当map[6][5] = 2就说明找到了通路
//规定策略,下->右->上->左
public boolean findWay(int[][] map, int i, int j){
if(map[6][5] == 2){
return true;
}else{
if(map[i][j] == 0){
map[i][j] = 2;
if(findWay(map, i + 1, j)){
return true;
}else if(findWay(map, i, j + 1)){
return true;
}else if(findWay(map, i - 1, j)){
return true;
}else if(findWay(map, i, j - 1)){
return true;
}else{
map[i][j] = 3;
return false;
}
}else{
return false;
}
}
}
}
xxxxxxxxxx
public class HanoiTower{
public static void main(String[] args){
Tower tower = new Tower();
tower.move(3, 'A', 'B', 'C');
}
}
class Tower{
//num表示要移动的个数,a,b,c分别表示A塔,B塔,C塔
public void move(int num, char a, char b, char c){
//如果只有一个盘
if(num == 1){
System.out.println(a + "->" + c);//指的是将A塔上的移动到C塔上
}else{
//如果有多个盘,可以看成是两个,最下面是一个,上面所有盘是一个 num-1
//先移动上面所有盘到b,借助c
move(num - 1, a, c, b);
//把最下面的盘移动到c
System.out.println(a + "->" + c);
//再把b塔的所有盘移动到c,借助a
move(num - 1, b, a, c);
}
}
}
定义:java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致
注意事项:
定义:java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成同一个方法
xxxxxxxxxx
public int sum(int... nums){
int res = 0;
for(int i =0; i < nums.length; i++){
res += nums[i];
}
return res;
}
注意事项:
基本使用
在java编程中,主要的变量就是属性(成员变量)和局部变量
局部变量一般指的是在成员方法中定义的变量(或代码块)
java中作用域的分类
全局变量可以不赋值,直接使用,因为有默认值;局部变量必须赋值后才能使用,因为没有默认值
注意事项:
属性和局部变量可以重名,访问时遵循就近原则
在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名
作用域范围不同
xxxxxxxxxx
public class test{
public static void main(String[] args){
Person p1 = new Person();
T t = new T();
t.test1();//第一种方法
t.test2(p1);//第二种方法
}
}
class T{//在类的方法中中调用其他类的属性
public void test1(){
Person p1 = new Person();
System.out.println(p1.name);
}
public void test2(Person p){
System.out.println(p.name);
}
}
class Person{
String name = "jack";
int age = 10;
}
修饰符不同
定义:是类的一种特殊方法,作用是完成对新对象的初始化。构造器对于方法较多的情况下非常好用。
基本语法
xxxxxxxxxx
[修饰符] 方法名 (形参列表){
方法体;
}
public class test{
public static void main(String[] args){
//new对象时,可以直接通过构造器指定名字和年龄
Person p1 = new Person("jack", 80);
}
class Person{
String name;
int age;
//创建构造器,完成对象属性的初始化
public Person(String pName, int pAge){
name = pName;
age = pAge;
}
}
使用细节:
看一个案例:
xxxxxxxxxx
class Person{
int age = 90;
String name;
Person(String n, int a){
name = n;
age = a;
}
}
Person p = new Person("小倩",20);
分析:
在方法区中先加载了Person类信息,只会加载一次
在堆中分配空间
完成对象初始化
把对象在堆中的地址返回给p(p是对象的引用)
对象里面隐藏一个this属性,this属性指向方法自己。简单地说,哪个对象调用,this就代表哪个对象
使用细节:
this可以用来访问本类的属性、方法、构造器
this可以区分当前类的属性和局部变量,即不受局部变量的影响,this.准确定位到属性
访问成员方法的语法:this.方法名(参数列表);
访问构造器语法:this(参数列表);(注意只能在构造器中调用另外一个构造器,且这条语句必须是构造器中的第一个语句)
this不能在类定义的外部使用,只能在类定义的方法中使用
this先调用本类的,然后再调用不同名的父类
xxxxxxxxxx
class T{
public T(){
this("jack", 10);//必须放在第一句话
System.out.println("T()构造器");
}
public T(String name, int age){
System.out.println("T(String name, int age)构造器");
}
}
比较两个人是否相同
xxxxxxxxxx
public class TestPerson{
public static void main(String[] args){
Person p1 = new Person("jack",21);
Person p2 = new Person("tom",21);
System.out.println(p1.compareTo(p2));
}
}
class Person{
String name;
int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public boolean compareTo(Person p){
return this.name.equals(p.name) && this.age == p.age;
}
}
包的简单介绍:
作用:
包的本质:创建不同的文件夹来保存类文件
包的命名
常用的包
如何导入包
注意事项
java提供四种访问控制修饰符,用于控制方法和属性的访问权限(范围)
注意事项:
封装的理解
好处:
封装的实现步骤
基本介绍:
基本语法:class 子类 extends 父类{}
带来的便利:
注意事项:
继承的本质
介绍
基本语法
super.属性名;
访问父类的属性,但不能访问父类的private属性super.方法名(参数列表)
访问父类的方法,但不能访问父类的private方法 使用细节
介绍:子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法
使用细节
xxxxxxxxxx
//重写equals方法,比较两个对象的属性是否相等
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(!(obj instanceof Doctor)){
return false;
}
Doctor doctor = (Doctor) obj;
return this.name.equals(doctor.name) &&
this.age == doctor.age &&
this.gender == doctor.gender &&
this.job.equals(doctor.job) &&
this.sal == doctor.sal;
}
介绍:方法和对象具有多种形态。
方法的多态:重写和重载
对象的多态(记住)
多态的细节讨论:
向上转型
本质:父类的应用指向了子类的对象
语法:父类类型 引用名 = new 子类类型();
调用方法的规则
向下转型
语法:子类类型 引用名 = (子类类型) 父类引用
调用规则
属性没有重写之说法。属性的值直接看编译类型
instanceof是比较操作符,用于判断对象的运行类型是否为XX类型或其子类型
==
equals
System.out.println(Person); == System.out.println(Person.toString());
快捷键
xxxxxxxxxx
访问修饰符 static 数据类型 变量名;//(推荐)
static 访问修饰福 数据类型 变量名;
//访问方式:
类名.类变量名;//(推荐)
对象名.类变量名;
注意事项:
xxxxxxxxxx
//推荐
访问修饰福 static 返回类型 方法名(){}
static 访问修饰福 返回类型 方法名(){}
//访问方式:
类名.类对象名();//(推荐)
对象名.类对象名();
使用:
使用细节:
xxxxxxxxxx
public static void main(String[] args){}
解释main方法的形式
基本介绍:
基本语法:[修饰符]{代码}; ;
号可以省略
好处:
说明注意:
修饰符是否写可选,写的话也只能写static。static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象都会执行一次
类什么时候被加载 [重要]
普通代码块会在创建对象实例时,被隐式的调用。如果只是使用类的静态成员,普通代码块不会被调用
创建一个对象时,类的调用过程是:
构造器的最前面隐含了super()和调用普通代码块
创建一个子类对象时,调用顺序:
静态代码块只能调用静态成员,普通代码块可以调用任意成员
介绍:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
饿汉式步骤
xxxxxxxxxx
public class single01 {
public static void main(String[] args) {
System.out.println(GirlFriend.getInstance());
}
}
class GirlFriend{
private String name;
private static GirlFriend gf = new GirlFriend("凌华");
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式步骤
xxxxxxxxxx
//只有当用户使用getInstance时,才返回cat对象。后面再次调用时,会返回上次创建的cat对象,从而保持了单例
public class single02 {
public static void main(String[] args) {
System.out.println(Cat.getInstance());
}
}
class Cat{
private String name;
private static Cat cat;
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
if(cat == null){
cat = new Cat("团子");
}
return cat;
}
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
小结:
基本介绍:可以修饰类、属性、方法、局部变量
应用场景:
使用细节
final修饰的属性又叫常量,一般用XX_XX_XX来命名
final修饰的属性在定义时必须赋初值,并且以后不能再改,赋值可以在如下的位置之一:
如果final修饰的属性是static的,则初始化的位置可以是:
final类不能继承,但能实例化对象
如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但可以被继承
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法了
final不能修饰构造器
final和static往往搭配使用,效率更高,不会导致类加载。底层编译器做了优化处理
包装类(Integer,Double,Float,Boolean,String等)都是final
介绍:
访问修饰符 abstract 类名
名叫抽象类访问修饰符 abstract 返回类型 方法名(参数列表);
名叫抽象方法。注意抽象方法没有方法体细节讨论:
模板设计模式
xxxxxxxxxx
abstract class Template{
public abstract void job();
public void calculateTime(){
long start = System.currentTimeMillis();
job();
long end = System.currentTimeMillis();
System.out.println((end - start));
}
}
xxxxxxxxxx
interface 接口名{
//属性
//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
class 类名 implements 接口{
//自己属性;
//自己方法;
//必须实现的接口的抽象方法;
}
//接口名.属性
//类名.属性
//类创建的对象名.属性
//这三种方式都能访问到接口内的属性
//在JDK7前,接口里的所有方法都没有方法体,都是抽象方法
//JDK8之后接口可以有静态方法,默认方法(default),也就是说接口中可以有方法的具体实现
使用细节
实现接口 VS 继承类
接口的多态特性
定义:局部内部类是定义在外部类的局部位置上的,通常在方法中
使用细节:
本质:
xxxxxxxxxx
//语法:
new 类或接口(参数列表){
类体
};
//调用方法:
new A(){//方法1,因为匿名内部类本质是返回对象
public void cry(){
}
}.cry();
=============
A a = new A(){//方法2,此处编译类型是A,运行类型是匿名内部类
public void cry(){
}
};
a.cry();
使用细节
使用场景:
xxxxxxxxxx
public class hhhhh {
public static void main(String[] args) {
f(new IB() {//匿名内部类传参法
public void show() {
System.out.println("show()");
}
});
f(new BB());//传统方法
}
public static void f(IB ib){//写一个方法传递接口类型参数
ib.show();
}
}
interface IB{
void show();
}
class BB implements IB{//硬编码
public void show() {
System.out.println("show()");
}
}
xxxxxxxxxx
public class Homework04 {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
cellPhone.testWork(new Computer() {//匿名内部类作为参数传递
public double work(double num1, double num2) {
return num1 + num2;
}
}, 10, 20);
cellPhone.testWork(new Computer() {
public double work(double num1, double num2) {
return num1 * num2;
}
}, 10, 5);
//这里可以看出匿名内部类是很方便的,可以重写来改变计算方法
}
}
interface Computer {//接口
public double work(double num1, double num2);
}
class CellPhone {//接口的方法只调用,让匿名内部类实现
public void testWork(Computer computer, double num1, double num2) {
double result = computer.work(num1, num2);
System.out.println(result);
}
}
定义:成员内部类是定义在外部类的成员位置,并且没有static修饰
使用细节
xxxxxxxxxx
public class innerClass01 {
public static void main(String[] args) {
new Outer().new Inner().say();//方式1
new Outer().getInner().say();//方式2
}
}
class Outer{
public class Inner{
private int n = 1;
public void say(){
System.out.println("ok");
}
}
public Inner getInner(){
return new Inner();
}
}
定义:静态内部类是定义在外部类的成员位置,并且有static修饰
使用细节
可以直接访问外部类的所有静态成员
可以添加任意访问修饰符
作用域为整个类体
外部其他类访问静态内部类方式
new Outer.Inner();
如果外部类和静态内部类的成员重名时,静态内部类访问时,遵循就近原则;如果想访问外部类的成员,则可以使用(外部类名.成员)来访问
实现方法
注意细节
使用
,
分隔xxxxxxxxxx
enum Season{
SPRING("春天", "温暖"),
SUMMER("夏天", "炎热"),
AUTUMN("秋天", "凉爽"),
WINTER("冬天", "寒冷");
private String name;
private String desc;
Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
注意事项
enum常用方法的使用
toString:Enum类已经重写过了,返回的是当前的对象名,子类也可以重写该方法,使其返回对象的属性信息
name:输出枚举对象的名称
ordinal:输出的是该枚举对象的编号(从0开始)
values:返回枚举类的数组,遍历枚举对象(原码内看不到values,但可以通过javap反编译看到)
xxxxxxxxxx
Season[] values = Season.values();
for(Season season: values){//增强for循环,values的值直接赋给season
System.out.println(season);
}
valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则会报错
compareTo:比较两个枚举变量,比较的是编号。返回值为编号相减
细节讨论
介绍:
xxxxxxxxxx
//@Override源码
ElementType.METHOD)//说明只能修饰方法,@Taeget是修饰注解的注解,成为元注解 (
RetentionPolicy.SOURCE) (
public @interface Override {
}
//@interface表示一个注解类,而不是接口
使用:
@SuppressWarning({""})
中可以写入希望抑制的警告信息类型说明各种值
类型
@Retention说明
只能用于修饰一个Annotation定义,用于指定其可以保留多长时间
包含一个RetentionPolicy类型的成员变量,三种值为
基本概念:java语言中,将程序执行中发生的不正常称为异常(不包括语法错误和逻辑错误)
分类:
运行异常
编译异常
xxxxxxxxxx
try{
//代码/可能有异常
}catch(Exception e){
//当异常发生时,系统将异常封装成Exception对象e,传递给catch,得到异常对象后,程序员自己处理。
//注意,如果没有发生异常,catch代码块不执行
}finally{
//不管try代码块是否有异常发生,始终要执行finally,finally可以不写
//所以通常将释放资源的代码放在finally中
}
注意事项
基本介绍
使用细节
步骤
throw和throws
定义:包装类就是基本数据类型对应的引用数据类型
除了Character和Boolean包装类,其余6个包装类的父类是Number
包装类和基本数据类型的转换(以int为例)
JDK5前的手动装箱:基本类型->包装类型,反之是拆箱
xxxxxxxxxx
int n1 = 100;
Integer integer = new Integer(n1);//过时
Integer integer1 = Integer.valueOf(n1);
int i = integer.intValue();
JDK5后实现了自动装箱和拆箱
xxxxxxxxxx
int n2 = 200;
Integer integer2 = n2;//底层仍然含有Integer.valueOf()
int n3 = integer2;
自动装箱底层调用的是valueOf方法,比如Integer.valueOf()
包装类型和String相互转换
xxxxxxxxxx
//Integer->String
Integer i = 10;
//方法1
String s1 = i + "";
//方法2
String s2 = String.valueOf(i);
//方法3
String s3 = i.toString();
//String->Integer
String s = "123";
//方法1
Integer i1 = Integer.valueOf(s);
//方法2
Integer i2 = new Integer(s);//已过时
Integer类和Character类的常用方法
String类实现了Serializable和Comparable接口,前者说明String可以串行化(可以进行网络传输),后者说明String对象可以相互比较
理解
本质:String有属性private final char values[];
用于存放字符串类型,且values是final类型,不可修改(地址)
xxxxxxxxxx
//方式一:直接赋值
String s = "asleer";
//方式二:调用构造器
String s1 = new String("asleer");
.intern()//可以返回常量池的地址
xxxxxxxxxx
String a = "abc";
String b = "def";
String c = a + b;
System.out.println("abc"+"drf" == c);//false
//常量相加和变量相加规则不同
xxxxxxxxxx
//1. equals比较字符串是否相等,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//false
//2. equalsIgnoreCase忽略大小写的判断内容是否相等
String username = "John";
System.out.println("john".equalsIgnoreCase(username));//true
//3. length获取字符的个数,字符串的长度
System.out.println("asleer".length());//6
//4. indexOf获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到返回-1
String s1 = "asleer@*(@)";
int index = s1.indexOf('@');
System.out.println(index);//6
System.out.println(s1.indexOf("ee"));//3
//5. lastIndexOf获取字符在字符串中最后一次出现的索引,如果找不到返回-1
index = s1.lastIndexOf('@');
System.out.println(index);//9
//6.substring截取指定范围的子串
String name = "hello,张三";
System.out.println(name.substring(6));//张三,从索引6开始截取后面的字符
System.out.println(name.substring(2,5));//llo,截取[2,5)位置的字符[2,5)
//7. trim删除前后空格
//8. charAt(n)获取某索引处的字符
xxxxxxxxxx
//1. toUpperCase
String s = "hello";
System.out.println(s.toUpperCase());//HELLO
//2. toLowerCase
System.out.println(s.toLowerCase());//hello
//3. concat拼接字符串
String s1 = "faker.";
s1 = s1.concat("uzi.").concat("theshy.").concat("together");
System.out.println(s1);//faker.uzi.theshy.together
//4. replace替换字符串中的字符
//s1.replace返回的结果才是替换过的,对s1本身没有影响
s1 = s1.replace(".", " ");
System.out.println(s1);//faker uzi theshy together
//5. split分割字符串,对于某些分割字符,需要转义
String poem = "a,b,c,d";
String[] split = poem.split(",");//abcd
poem = "E:\\aa\\bb";
split = poem.split("\\\\");//E:aabb
for (int i = 0; i < split.length; i++) {
System.out.print(split[i]);
}
//6. toCharArray转换成字符数组,再调用String构造器可以转换回去
s = "happy";//拆开了分行输出happy
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
//7. compareTo 比较两个字符串的大小
//比较第一个不同的字符之间相差多少
//如果都相等则比较长度
String a = "tca";
String b = "tam";
System.out.println(a.compareTo(b));//2
String c = "tama";
String d = "tam";
System.out.println(c.compareTo(d));//1
//8. format格式字符串
String name = "ll";
int age = 18;
double score = 56.789;
char gender = '男';
//占位符
//%s表示由后面变量来替换
//%d是整数来替换
//%.2f表示使用小数来替换,保留两位小数并且四舍五入
//%c使用char类型来替换
String formatStr = "我的名字是%s,年龄是%d,成绩是%.2f,性别是%c";
String info = String.format(formatStr,name,age,score,gender);
System.out.println(info);
结构剖析
StringBuffer构造器
StringBuffer转换
xxxxxxxxxx
//String->StringBuffer
String str = "hello";
//方式1 使用构造器
StringBuffer stringBuffer1 = new StringBuffer(str);
//方式2 使用append方法
StringBuffer stringBuffer2 = new StringBuffer();
stringBuffer2 = stringBuffer2.append(str);
//StringBuffer->String
StringBuffer stringBuffer3 = new StringBuffer("hello");
//方式1 使用StringBuffer提供的toString方法
String s = stringBuffer3.toString();
//方式2 使用构造器
String s1 = new String(stringBuffer3);
xxxxxxxxxx
StringBuffer s = new StringBuffer("hello");
//1. append增加
s.append(',');
s.append("world");
//2. delete删除
s.delete(5,8);//删除索引为[5,8),即hellorld
//3. replace替换
s.replace(5,8,",asleer");//hello,asleer
//4. indexOf查找指定字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("asleer");//6
//5. insert在索引的位置插入内容
s.insert(6,"my");//hello,myasleer
//6. length长度
System.out.println(s.length());//14
基本介绍
结构剖析
String、StringBuffer、StringBuilder
abs:绝对值
pow:求幂指数
ceil:向上取整
floor:向下取整
round:四舍五入
sqrt:求开方
random:求0~1内的随机数
求2~7之间的一个随机整数
(int)(2 + Math.random() * (5 + 1));
max:求两个数的最大值
min:求两个数的最小值
xxxxxxxxxx
Arrays.sort(arr);
Arrays.sort(arr, new Comparator(){
public int compare(Object o1, Object o2){
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
});//倒序,使用匿名内部类实现接口方法的自定义实现
//定制排序,定制冒泡排序法
public static void main(String[] args) {
int[] arr = {1, -1, 8, 0, 20};
Bubble(arr, new Comparator() {
public int compare(Object o1, Object o2) {
int i1 = (Integer) o1;
int i2 = (Integer) o2;
return i2 - i1;//可以通过返回值改变排序顺序
}
});
System.out.println(Arrays.toString(arr));
}
public static void Bubble(int[] arr, Comparator c){
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++){
if(c.compare(arr[j], arr[j + 1]) > 0){
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
return -(low + 1);
Arrays.copyOf(arr, arr.length());
从arr数组中,拷贝arr.length个元素到新数组中。如果拷贝的长度>arr.length就在新数组的后面增加null。如果拷贝长度<0,就抛出NegativeArraySizeException。该方法的底层使用的是System.arraycopy()List asList = Arrays.asList(2, 3, 4, 5, 6, 1);
返回的asList编译类型是List(接口),运行类型是java.util.Arrays$ArrayList,是Arrays类的内部类常见方法
System.arraycopy(src, 0(srcPos), dest, 0(destPos), src,length)
BigInteger
xxxxxxxxxx
BigInteger bigInteger = new BigInteger("1234567890987654321");
BigInteger bigInteger1 = new BigInteger("100");
BigInteger add = bigInteger.add(bigInteger1);//加法
BigInteger subtract = bigInteger.subtract(bigInteger1);//减法
BigInteger multiply = bigInteger.multiply(bigInteger1);//乘法
BigInteger divide = bigInteger.divide(bigInteger1);//除法
BigDecimal
xxxxxxxxxx
BigDecimal bigDecimal = new BigDecimal("12345678.90987654321");
BigDecimal bigDecimal1 = new BigDecimal("100.1");
BigDecimal add = bigDecimal.add(bigDecimal1);//加法
BigDecimal subtract = bigDecimal.subtract(bigDecimal1);//减法
BigDecimal multiply = bigDecimal.multiply(bigDecimal1);//乘法
BigDecimal divide = bigDecimal.divide(bigDecimal1, RoundingMode.CEILING);//除法
//可能抛出异常ArithmeticException,如果除不尽出现无限循环小数的话
//在调用divide方式时,指定一个精度即可
xxxxxxxxxx
//1. 获取当前系统时间,在java.util包下
//默认输出的日期格式是国外的方式,因此通常需要对格式进行转换
Date date = new Date();
System.out.println(date);
//2. 创建SimpleDateFormat对象,可以指定相应的格式
//format:将日期转换成指定格式的字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(date);
System.out.println(format);
//3. 通过指定毫秒数得到时间
Date date1 = new Date(9999999);
//4. String->Date,使用的sdf格式需要和你给的String的格式一致,否则会抛出转换异常
String s = "2022年07月21日 11:37:22 星期四";
Date parse = sdf.parse(s);
System.out.println(parse);
Calendar类(日历)
xxxxxxxxxx
Calendar c = Calendar.getInstance();
System.out.println(c);//列出日历类对象的方法
System.out.println(c.get(Calendar.HOUR));//返回小时,所获得的小时数是12进制的。24进制为HOUR_OF_DAY
//月份是从0开始计数的,所以要+1后输出
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH));
前两代日期类的不足分析:
JDK1.0中包含的java.util.Date类大多数方法都在JDK1.1引入Calendar类后被弃用了。而Calendar存在的问题为:
第三代日期类在JDK8加入
xxxxxxxxxx
//1. 使用now()返回当前日期时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//2. 使用DateTimeFormatter对象来进行格式化
DateTimeFormatter d = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
String format = d.format(ldt);
System.out.println(format);
//3. Instant时间戳,类似于Date
Instant now = Instant.now();
Date date = Date.from(instant);//Instant->Date
Instant instant = date.toInstant();//Date->Instant
ldt.getYear();
ldt.getMonth();//英文的月
ldt.getMonthValue();//数字的月
ldt.getDayOfMonth();
ldt.getHour();
ldt.getMinute();
ldt.getSecond();
在开发中选择什么集合实现类,主要取决于业务特点
先判断存储的类型:一组对象(单列)或一组键值对(双列)
一组对象:Collection接口
允许重复:List
不允许重复:Set
一组键值对:Map
Collection接口常用方法
Collection接口遍历元素方式1—Iterator(迭代器)
xxxxxxxxxx
Collection col = new ArrayList();
col.add(new Book("三国","罗贯中", 10.5));
col.add(new Book("水壶","吴承恩", 12.5));
col.add(new Book("红楼","曹雪芹", 4.5));
//1. 先得到col对应的迭代器
Iterator iterator = col.iterator();
//2. 使用while循环遍历,快捷键为itit
//hasNext:判断是否还有下一个元素
while (iterator.hasNext()) {
//next():1.指针下移 2.将下移以后的位置上的元素返回
Object next = iterator.next();
System.out.println(next);
}
//3. 当退出while循环后,iterator迭代器指向最后的元素
iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置迭代器
iterator = col.iterator();
Collection接口遍历元素方式1—for循环增强
增强for在底层仍然是Iterator迭代器
快捷方式:
I
xxxxxxxxxx
for (Object book : col) {
System.out.println(book);//输出和上面一模一样
}
基本介绍
List接口的常用方法
list.add(1, "hello");
默认在索引为1的位置插入hellolist.addAll(1, list2);
,在索引为1的位置插入list2中的所有元素list.set(1, "marry");
,替换索引为1的位置的元素为marrylist.subList(int fromIndex, int toIndex)
返回[fromIndex, toIndex)的子集合List接口的遍历方法
xxxxxxxxxx
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
ArrayList
注意事项
ArrayList底层结构和源码分析
transient Object[] elementData;
//transient表示瞬间的、短暂的,表示该属性不会被序列化Vector
Vector注意事项
protected Object[] elementData;
Vector底层结构和源码剖析
LinkedList
LinkedList注意事项
LinkedList底层实现了双向链表和双端队列特点
可以添加任意元素(元素可以重复),包括null
线程不安全,没有实现同步
改查的操作多选择ArrayList,增删的操作多选择LinkedList。一般使用ArrayList
Set接口基本介绍
Set接口常用方法和Collection一样
Set接口的遍历方式
同Collection的遍历方式
HashSet
说明
底层机制分析:add如何实现
HashSet扩容和转成红黑树机制
16*2=32
,新的临界值是32*0.75=24
,依次类推。LinkedHashSet
说明
注意事项
Map接口实现类的特点
解读
Entry<Map.Entry<k,v>>
Map接口常用方法
Map接口遍历方法
Set keyset = hashMap.keySet();
Collection values = hashMap.values();
Set entrySet = hashMap.entrySet();
xxxxxxxxxx
HashMap hashMap = new HashMap();
hashMap.put(1,"asl");
hashMap.put(2,"lsgaf");
hashMap.put(3,"sssl");
//1. 先取出所有的key,通过key取出对应的value
Set keyset = hashMap.keySet();
//(1)增强for
for (Object key : keyset) {
System.out.println(key + "-" + hashMap.get(key));
}
//(2)iterator迭代器
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + hashMap.get(key));
}
//2. 把所有的values取出
Collection values = hashMap.values();
//这里可以使用Collections使用的遍历方法
//(1) 增强for
for (Object value : values) {
System.out.println(value);
}
//(2) iterator迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
//3. 通过EntrySet来获取k-v
Set entrySet = hashMap.entrySet();
//(1) 增强for
for (Object entry : entrySet) {
//将entry转成Map.Entry,向下转型
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) iterator迭代器
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Object entry = iterator2.next();
//System.out.println(entry.getClass());//HashMap$Node-实现->Map.Entry(getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
//System.out.println(m.getClass());//HashMap$Node,所谓万变不离其宗
System.out.println(m.getKey() + "-" + m.getValue());
}
说明
HashMap底层原理和扩容机制和上面的HashSet完全相同。区别是HashSet是相同key会无法加入,HashMap相同key会替换value
介绍
Hashtable底层和扩容机制
int newCapacity = (oldCapacity << 1) + 1
Hashtable和HashMap对比
介绍
Properties类继承Hashtable类并实现了Map接口,使用k-v键值对保存数据
使用特点和hashtable类似
Properties可以用于从xxx.properties文件中加载数据到Properties类对象,并进行读取和修改。说明:xxx.properties文件通常作为配置文件
TreeSet
xxxxxxxxxx
TreeSet treeSet = new TreeSet(new Comparator() {
public int compare(Object o1, Object o2) {
return ((String) o2).length() - ((String) o1).length();//比较长度
//return ((String) o2).compareTo((String) o1);//比较字符
}
});
treeSet.add("c");
treeSet.add("cee");
treeSet.add("agggg");
treeSet.add("fff");//添加失败,因为已经有3个长度的key了
System.out.println(treeSet);
xxxxxxxxxx
TreeMap treeMap = new TreeMap(new Comparator() {
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
//return ((String) o2).compareTo((String) o1);
}
});
treeMap.put("a","琪亚娜");//第一次添加,把k-v封装到Entry对象,放入root,调用了compare方法了,检测是否为null会抛出异常
treeMap.put("bb","芽衣");
treeMap.put("ccc","布若妮亚");
treeMap.put("asdf","草履虫");
treeMap.put("kzlz","空之律者");//key=kzlz添加失败,因为存在4个长度的key,但value=“空之律者”会替换“草履虫”
System.out.println(treeMap);
Collection工具类介绍
排序操作
查找和替换
xxxxxxxxxx
ArrayList dest = new ArrayList();
for(int i = 0; i < list.size(); i++){//为了完成一个完整的拷贝,需要先给dest赋值,大小和list.size()一样
dest.add("");
}
Collections.copy(dest, list);
dest.addAll(list);//这种方式也能直接拷贝
试分析HashSet和TreeSet分别如何实现去重的
泛型的好处
泛型说明
xxxxxxxxxx
//声明
interface 接口<T>{}
class 类<k,v>{}
//其中T,K,V不代表值,而是表示类型
//任何字母都可以,常用T表示,是type的缩写
//实例化
List<String> strList = new ArrayList<>();
Iterator<Customer> iterator = cunsomers.iterator();
注意事项和细节
xxxxxxxxxx
class 类名<T,R...>{成员}
注意细节
xxxxxxxxxx
interface 接口名<T,R...>{成员}
注意细节
xxxxxxxxxx
修饰符 <T,R...>返回类型 方法名(参数列表){}
class Cat{
public void run(){};
public<T,R> void fly(T t, R r){};
}
class Fish<T,R>{
public void swim(){};//普通方法
public<U,M> void eat(U u, M m){};//泛型方法
public void hi(T t){};//修饰符后没有泛型的方法不是泛型方法,而是使用了泛型
}
注意细节
说明
List<Object> list = new ArrayList<String>();
是不正确的JUnit
什么是线程
其他相关概念
继承Thread就是开启了一个子线程,所有线程执行完后,进程才会退出
start方法
xxxxxxxxxx
Student student = new Student();
Thread thread = new Thread(student);
thread.start();
xxxxxxxxxx
public class Runnable_ {
public static void main(String[] args) {
Student student = new Student();
ThreadProxy threadProxy = new ThreadProxy(student);
threadProxy.start();
}
}
class Person{}
class Student extends Person implements Runnable{
public void run() {
System.out.println("学生学习中...");
}
}
class ThreadProxy implements Runnable{//极简地模拟了Thread内部结构
private Runnable target = null;
public void run() {
if(target != null){
target.run();
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
}
实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。建议使用Runnable
线程终止
常用方法1:
注意事项和细节
常用方法2:
用户线程和守护线程(Daemon)
setDaemon()
),后开启线程start()xxxxxxxxxx
//example:使用线程X2控制线程X1,可以使用守护线程,或者使用线程X2控制loop
public class Homework01 {
public static void main(String[] args) {
X1 x1 = new X1();
X2 x2 = new X2();
x1.setDaemon(true);//启动守护线程,X2输入Q退出时,X1就退出
x1.start();
x2.start();
}
}
class X1 extends Thread{
static boolean loop = true;
public void run() {
while(loop){
System.out.println((int)(Math.random() * 100 + 1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/*public static void setLoop(boolean loop) {
X1.loop = loop;
}*/
}
class X2 extends Thread{
Scanner scanner = new Scanner(System.in);
public void run() {
while(true){
System.out.println("请输入Q退出");
char c = scanner.next().toUpperCase().charAt(0);
if(c == 'Q'){
//X1.setLoop(false);
break;
}
}
}
}
七大线程状态
New:尚未启动的线程处于此状态
Runnable:在java虚拟机中执行的线程处于此状态
Blocked:被阻塞等待监视器锁定的线程处于此状态
Waiting:正在等待另一个线程执行特定动作的线程处于此状态
TimedWaiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
Teminated:已退出的线程处于此状态
线程同步机制
同步具体方法
xxxxxxxxxx
//1.同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步的代码
}
//2.synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(){
//需要被同步的代码
}
Thread.currentThread().getName()
返回当前线程的名字基本介绍
注意事项和细节
同步方法如果没有使用static修饰:默认锁对象this,this对象锁是非公平锁
同步方法使用static修饰:默认所当前类.class
实现的落地步骤
基本介绍
释放锁的操作
不会释放锁的操作
文件流
常用的文件操作
xxxxxxxxxx
//1. new File(String pathname),根据路径构建一个File对象
public void Create01() throws IOException {
String filePath = "e:\\news1.txt";//这里也可以使用"e:/news1.txt",但推荐前者
File file = new File(filePath);
file.createNewFile();
}
//2. new File(File parent, String child),根据父目录文件+子路径构建
public void Create02() throws IOException {
File parentFile = new File("e:\\");
String fileName = "news2.txt";
File file = new File(parentFile, fileName);
//这里的file对象,在java程序中只是一个对象
//只有执行了对象的createNewFile方法,才会真正的在磁盘创建该文件
file.createNewFile();
}
//3. new File(String parent, String child),根据父目录+子路径构建
public void Create03() throws IOException {
String parentPath = "e:\\";
String fileName = "news3.txt";
File file = new File(parentPath, fileName);
file.createNewFile();
}
获取文件信息
目录操作和文件删除(目录又叫文件夹,可以当做一种特殊的文件)
java IO流原理
流的分类
IO流体系图
基本介绍
图解:2-5:节点流,6后:处理流
节点流和处理流的区别和联系
xxxxxxxxxx
//1. Reader_抽象类,在抽象类中使用read方法统一管理
public abstract class Reader_ {
public abstract void read();
}
//2. FileReader_实现Reader_
public class FileReader_ extends Reader_{
public void read(){
System.out.println("读取文件...");
}
}
//3. StringReader_实现Reader_
public class StringReader_ extends Reader_ {
public void read() {
System.out.println("读取字符串...");
}
}
//4. BufferedReader_将实现了Reader_的类封装
public class BufferedReader_ extends Reader_ {
private Reader_ reader_;
public BufferedReader_(Reader_ reader_) {
this.reader_ = reader_;
}
public void read(){
reader_.read();
}
public void read(int num){//在不改变原有方法的基础上扩展功能
for (int i = 0; i < num; i++) {
reader_.read();
}
}
}
//5. 测试
public class Test{
public static void main(String[] args) {//对象的动态绑定机制
BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
bufferedReader_.read(10);
BufferedReader_ bufferedReader_1 = new BufferedReader_(new StringReader_());
bufferedReader_1.read(5);
}
}
处理流的功能
标准输入流:System.in
public final static InputStream in = null
标准输出流:Syetem.out
public final static PrintStream out = null
InputStreamReader和OutoutStreamWriter
xxxxxxxxxx
public static void main(String[] args) throws IOException {
String filePath = "e:\\hello.txt";
//把FileInputStream转成InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//把InputStreamReader转成BufferedReader,可以和上面合在一起写
BufferedReader br = new BufferedReader(isr);
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
br.close();
}
可以看到PrintStream可以输入文件,字符串,字节流,PrintWriter可以放入文件,字符串,字节流,字符流
xxxxxxxxxx
//只演示PrintStream,PrintWriter同理
PrintStream out = System.out;
//或是PrintStream out = new PrintStream(System.out);
//默认情况下,PrintStream输出数据的位置是标准输出
out.print("hello");
//print底层使用的是write,所以可以直接调用write进行打印
out.write("hello?".getBytes());
out.close();
//我们可以去修改打印流输出的位置,setOut调用了native属性的setOut0方法
System.setOut(new PrintStream(filePath));
System.out.println("hello");//这个时候不会打印在显示器,会打印在filePath位置
xxxxxxxxxx
public void readFile() throws IOException {
String filePath = "e:\\hello.txt";
byte[] buf = new byte[8];//一次性读取8个字节
int readLen = 0;
FileInputStream fileInputStream = null;
fileInputStream = new FileInputStream(filePath);
while((readLen = fileInputStream.read(buf)) != -1){//如果返回-1,表示读取完毕
System.out.print(new String(buf, 0, readLen));
}
fileInputStream.close();//关闭文件流,释放资源
}
xxxxxxxxxx
public void writeFile() throws IOException {
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
//1. new FileOutputStream(filePath)创建方式,当写入内容时会覆盖掉原来的内容
//2. new FileOutputStream(filePath, true)创建方式,当写入内容时会追加到文件后面
fileOutputStream = new FileOutputStream(filePath);
//写入一个字节
fileOutputStream.write('H');
//写入字符串
String str = "hello,world???";
//字符串->字节数组
fileOutputStream.write(str.getBytes());
//将len字节从偏移量off的指定字节数组写入此文件输入流
fileOutputStream.write(str.getBytes(), 1, 3);//ell
fileOutputStream.close();
}
xxxxxxxxxx
public static void main(String[] args) throws IOException {
String srcfilePath = "e:\\hello.jpg";
String destfilePath = "e:\\ssss.jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcfilePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destfilePath));
byte[] buf = new byte[1024];
int readLen;
while((readLen = bis.read(buf)) != -1){
bos.write(buf, 0, readLen);
}
bis.close();
bos.close();
}
处理流(此处也可叫对象流):ObjectInputStream和ObjectOutputStream
序列化和反序列化
序列化:保存数据时,保存数据的值和数据类型,ObjectOutputStream提供了此功能
反序列化:恢复数据时,恢复数据的值和数据类型,ObjectInputStream提供了此功能
需要让某个类的对象支持序列化机制,则必须让其类是可序列化的,必须实现如下两个接口之一:
细节说明
xxxxxxxxxx
//1. ObjectOutputStream
public static void main(String[] args) throws IOException {
//序列化后,文件是按照他的格式来保存的
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeInt(100);//int->Interget
oos.writeBoolean(true);
oos.writeChar('a');//char->character
oos.writeDouble(9.5);//double->Double
oos.writeUTF("薪炎之律者");//String本身实现了Serializable
oos.writeObject(new Dog("旺财", 3));
oos.close();
}
public class Dog implements Serializable {
private String name;
private int age;
private static final long serialVersionUID = 1L;//序列化的版本号,可以提高兼容性
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
//Getter和Setter方法
//重写toString
}
//2. ObjectInputStream
public static void main(String[] args) throws IOException, ClassNotFoundException {
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取(反序列化)的顺序需要和保存数据(序列化)的顺序一致,否则会出现异常
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println(dog.getClass());
System.out.println(dog);
//1. 如果需要调用Dog方法,需要向下转型
//2. 需要我们将Dog类的定义,拷贝到可以引用的地方
Dog dog1 = (Dog) dog;
System.out.println(dog1.getName());
ois.close();
}
FileReader
常用方法
相关API
xxxxxxxxxx
public void FileReader_() throws IOException {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
char[] chars = new char[8];
int readLen = 0;
fileReader = new FileReader(filePath);
while((readLen = fileReader.read(chars)) != -1){
System.out.print(new String(chars, 0, readLen));
}
fileReader.close();
}
FileWriter
常用方法
相关API
注意:FileWriter使用后,必须关闭(close)或刷新(flush),否则写不到指定的文件
BufferedReader
xxxxxxxxxx
public static void main(String[] args) throws IOException {
String filePath = "e:\\story.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
String line;//按行读取效率高
while((line = bufferedReader.readLine()) != null){//返回null时读取完毕
System.out.println(line);
}
bufferedReader.close();
}
xxxxxxxxxx
public static void main(String[] args) throws IOException {
String filePath = "e:\\hello.txt";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));//追加
bufferedWriter.write("asleer,hello1");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("asleer,hello2");
bufferedWriter.newLine();
bufferedWriter.write("asleer,hello3");
bufferedWriter.newLine();
bufferedWriter.close();
}
基本介绍
Properties方法
读取文件
xxxxxxxxxx
public static void main(String[] args) throws IOException {
//1. 创建Properties对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("test_project01\\src\\com\\chapter2_8\\Properties_\\mysql.properties"));
//3. 把k-v显示在控制台
properties.list(System.out);
//4. 根据key获取对应的值
String user = properties.getProperty("user");
System.out.println(user);
}
xxxxxxxxxx
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.setProperty("charset", "utf8");
properties.setProperty("user", "老王");//会存储为unicode码
properties.setProperty("pwd", "369369");
properties.store(new FileOutputStream("test_project01\\src\\com\\chapter2_8\\Properties_\\mysql2.properties"), null);
System.out.println("配置成功");
}
域名
www.baidu.com
端口号
TCP协议:传输控制协议
UDP协议:用户数据协议
相关方法
基本介绍
xxxxxxxxxx
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//1. 在本机9999端口监听,等待连接,要求在本机没有其他服务在监听9999
// 这个ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在9999端口监听,等待连接...");
//2. 当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有用户连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket = " + socket.getClass());
//3. 通过socket.getInputStream读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
//4. IO读取
byte[] buf = new byte[1024];
int readLen;
while((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf, 0, readLen));
}
//5. 关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();
}
}
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//1. 连接服务端
// 连接本机的9999端口,如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket = " + socket.getClass());
//2. 连接上后,生成Socket对象,通过socket.fetOutputStream
// 得到和socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到数据通道
outputStream.write("hello, server".getBytes());
//4. 关闭流对象和socket
outputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
案例2:客户端和服务端互发消息
socket.shutdownOutput()
write.newLine()
作为结束标记,但要求读取时使用readLine()
来读取。但推荐使用socket.shutdownOutput()
xxxxxxxxxx
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while((line = br.readLine()) != null){//这里传入多句时,没有shutdownOutout会走不动
System.out.println(line);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("hello,clinet");
bw.newLine();
bw.flush();
socket.shutdownOutput();
bw.close();
br.close();
socket.close();
serverSocket.close();
}
}
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("hello,server");
bw.newLine();
bw.flush();
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = br.readLine();
System.out.println(s);
br.close();
bw.close();
socket.close();
}
}
netstat -an:可以查看当前主机网络情况,包括端口监听情况和网络连接情况
netstat -an|more:可以分页显示
netstat -anb:可以显示地址对应的程序
要求在dos控制台执行,win+r
说明
当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端通讯的,这个端口是TCP/IP来分配的,是随机的
基本介绍
基本流程
xxxxxxxxxx
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个DatagramSocket对象,准备在9999端口接受数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个DatagramPacket对象,准备接受数据
// 一个数据报最大64K
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用接受方法,将通过网络传输的DatagramPacket对象填充到packet对象
System.out.println("接收端A等待接受数据...");
socket.receive(packet);
//4. 可以把packet进行拆包,取出数据并显示
int length = packet.getLength();//实际接受到的数据字节长度
byte[] data = packet.getData();//接受到的数据
String s = new String(data, 0, length);
System.out.println(s);
//5. 关闭资源
socket.close();
System.out.println("A端退出");
}
}
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1. 创建DatagramSocket对象,准备在9998端口接受数据
DatagramSocket socket = new DatagramSocket(9998);
//2. 将需要发送的数据封装到DatagramPacket对象
byte[] data = "hello 明天吃火锅".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,
InetAddress.getByName("172.25.252.138"), 9999);
socket.send(packet);
socket.close();
System.out.println("B端退出");
}
}
xxxxxxxxxx
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
FileOutputStream fileOutputStream = new FileOutputStream("picture.jpg");
while((readLen = inputStream.read(buf)) != -1){
fileOutputStream.write(buf, 0, readLen);
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("收到消息".getBytes());
socket.shutdownOutput();
outputStream.close();
fileOutputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
String filePath = "e:\\明日方舟图.jpg";
FileInputStream fileInputStream = new FileInputStream(filePath);
byte[] buf = new byte[1024];
int readLen = 0;
OutputStream outputStream = socket.getOutputStream();
while((readLen = fileInputStream.read(buf)) != -1){
outputStream.write(buf, 0, readLen);
}
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
readLen = 0;
while((readLen = inputStream.read(bytes)) != -1){
System.out.println(new String(bytes, 0, readLen));
}
inputStream.close();
outputStream.close();
fileInputStream.close();
socket.close();
}
}
xxxxxxxxxx
public class Homework03Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("9999正在监听...");
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen;
String s = "";
while((readLen = inputStream.read(buf)) != -1){
s = new String(buf, 0, readLen);
}
System.out.println(s);
if(s.equals("齐天")||s.equals("魔女之旅")){
s += ".mp3";
}else{
s = "齐天.mp3";
}
String filePath = "src\\chapter3_1\\socket\\homework\\" + s;
FileInputStream fileInputStream = new FileInputStream(filePath);
OutputStream outputStream = socket.getOutputStream();
byte[] music = new byte[1024];
while((readLen = fileInputStream.read(music)) != -1){
outputStream.write(music, 0, readLen);
}
socket.shutdownOutput();
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("程序结束退出");
}
}
public class Homework03Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入歌曲名");
String s = scanner.next();
outputStream.write(s.getBytes());
socket.shutdownOutput();
byte[] music = new byte[1024];
int readLen;
InputStream inputStream = socket.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("e:\\" + s + ".mp3");
while((readLen = inputStream.read(music)) != -1){
fileOutputStream.write(music, 0, readLen);
}
fileOutputStream.close();
inputStream.close();
outputStream.close();
socket.close();
System.out.println("程序结束退出");
}
}
反射的优点和缺点
反射调用优化——关闭访问检查
基本介绍
xxxxxxxxxx
String classAllPath = "chapter3_2.Car";
//1. 获取到Car类对应的Class对象,<?>表示不确定的java类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出cls
System.out.println(cls);//显示cls对象是哪个类的Class对象 class chapter3_2.Car
System.out.println(cls.getClass());//输出运行类型 class java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//chapter3_2
//4. 得到全类名
System.out.println(cls.getName());//chapter3_2.Car
//5. 通过cls创建对象实例
Car car = (Car)cls.getDeclaredConstructor().newInstance();
System.out.println(car);//car.toString
//6. 通过反射获取属性brand
Field brand = cls.getField("brand");
//car.brand不能用,因为car里面没有getter方法,只能使用反射得到
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");//真正的Car类里brand是没变的,变得是car对象的brand
System.out.println(brand.get(car));//奔驰
//8. 得到所有的属性
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName());//获取字段名称
System.out.println(f.get(car));
}
Class.forName()
Class<?> aclass = Class.forName("chapter3_2.Car");
类名.class
Class<Car> carClass = Car.class;
对象名.getClass()
Class<? extends Car> aClass = car.getClass();
类加载器
实例:
xxxxxxxxxx
ClassLoader classLoader = car.getClass().getClassLoader();
Class<?> aClass1 = classLoader.loadClass("chapter3_2.Car");
基本数据类型
Class<Integer> integerClass = int.class;
基本数据类型对应的包装类
Class<Character> type = Character.TYPE;
如下成员有Class对象
基本说明
类加载时机