
java复习
一.javac.exe java.exe javap.exe javadoc.exe:
javac.exe: Java编译器,将 .java 文件编译成 .class 文件,即将源代码文件编译成字节码文件,字节码文件还不是机器能直接运行的文件。 javac.exe 编译后得到的 .class 文件是二进制指令,但不是机器指令,而是 Java 虚拟机可识别的指令。
java.exe: Java解释器,启动 JVM(Java虚拟机),将 .class 文件一行一行地解释成机器指令执行。(由 Java 虚拟机对字节码进行解释和执行) 这就是为什么 Java 程序能跨平台运行的原因。无论是在Windows、Linux、MAC等系统上,只要装有相应版本的 JVM,该JVM 就能把 .class 文件解释成机器指令执行。实现“一次编译,随处运行”。
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区 (字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。
一般常用的是 -v -l -c 三个选项
javap -l 会输出行号和本地变量表信息
javap -c 会对当前 class 字节码进行反编译生成汇编代码。
java -v classxx 除了包含 -c 内容外,还会输出行号,局部变量表信息、常量池灯信息。不包含私有信息。
java -v -p classxx 最全的,包括所有的。
javadoc是Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档。也就是说,只要在编写程序时以一套特定的标签作注释,在程序编写完成后,通过Javadoc就可以同时形成程序的开发文档了。
javadoc命令是用来生成自己API文档的,使用方式:使用命令行在目标文件所在目录输入javadoc +文件名.java。
二.Java的输入输出:
输入:Scanner类
一、Scanner类简介\nScanner类是在jdk1.5版本引入的,它在java的util工具包下,主要用于扫描用户从控制台输入的文本。\n当我们需要通过控制台输入数据时,只需要事先导入java.util包中的Scanner类,然后调用Scanner类,\n我们的程序就能获取我们在控制台所输入的数据了。
二、如何使用Scanner类
1.首先导入java.util.Scanner包;
import java.util.Scanner;
2.创建Scanner类对象
Scanner sc = new Scanner(System.in);
3.创建一个变量来接收输入的数据
运用上面创建的sc对象调用对应的方法,控制台即可等待用户输入,我们自定义一个变量来接收即可
我们想要输入的数据类型不同,也需要调用不同的方法,具体应用如下:
String str = sc.nextLine();
String str = sc.next();
byte a1= sc.nextByte();
short a2= sc.nextShort();
int a3 = sc.nextInt();
long a4 = sc.nextLong();
float a5 = sc.nextFloat();
double a6 = sc.nextDouble();
boolean a7 = sc.nextBoolean();
4.关闭Scanner类
//使用完Scanner后,我们一定要记得将它关闭!
//因为使用Scanner本质上是打开了一个IO流,如果不关闭的话,它将会一直占用系统资源。
sc.close();
//但是注意一旦你关闭后,就算在sc.close()这行代码后你再重新new Scanner(System.in),那也不能重新再打开一个扫描器了,如下图会发现程序会报错
//所以大家一定要在用不到扫描器之后再关闭,即把sc.close()代码放到最后。
三、next()和nextLine()的区别(很重要)\n从上面的讲解,我们会发现当我们想要输入的数据类型是String时,我们有两个方法可以调用:next()和nextLine()。那这两种方法具体有什么区别呢?我们继续往下看:
next()用法总结:
\1. 一定要读取到有效字符后才可以结束输入。
\2. 对输入的有效字符之前所遇到的空白,会自动将其去除。
\3. 只有输入的有效字符后才将其后面输入的空白作为结束符。
\4. next()不能得到带有空格的字符串。
\5. 读取结束后,该方法会将我们的鼠标定位在我们输入数据的那一行。
nextLine()用法总结:
1、以回车符作为结束标识符,获取到的是回车符前输入的所有字符串(包括空格)。
2、读取结束后,该方法会将我们的鼠标定位在我们输入数据的那一行的下一行。
(3)先使用nextLine再使用next()、nextInt()等没问题,但是先使用next()和nextInt()等之后就不可以再紧跟nextLine使用。(这一点很重要!!!)如果数据在同一行以空格隔开,则int类型数据读取成功,String类型数据会多一个字符串,如果数据以回车隔开,在输入int数据后按回车就将输出语句执行,有时还报数据输入出错异常。
原因:nextInt()方法根据分隔符比如空格,回车符等从输入流中分割出第一部分将其存入已经定义的int类型的数据中,然后将控制台输入的其他字符传递下去,所以按照上述代码程序从控制台得到了“1+enter”,于是n得到了1,回车留给了str,接着输入自己想输入的字符串就会报错。
解决办法:
1、不要求输入数据的类型先后时,将nextLine()放在nextInt()之前进行控制台输入
2、要求输入数据的输入顺序时,在nextInt()后加两个sc.nextLine(),第一个去解决那个多余的字符,第二个用来读取。
四、hash×××()的简单使用,判断输入的数据是否为xxx类型。
输出:print,println,printf`
print()输出完毕后不换行,而println()输出完毕后会换行,因此
println()不输出任何东西时,就输出一个换行符
printf()沿用了C语言的用法
三.String及StringBuffer:
1、String与StringBuffer的区别的
简单地说,就是一个变量和常量的关系.StringBuffer对象的内容可以修改;而字符串对象一旦产生后就不可以被修改,重新赋值其实是两个对象
StringBuffer的内部实现方式和字符串不同,StringBuffer的在进行字符串处理时,不生成新的对象,在内存使用上要优于串类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入,删除等操作,使用StringBuffer要更加适合一些。
字符串:
在String类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个Java 字符串中的某个单独字符,所以在JDK文档中称String类的对象是不可改变的,然而,不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。 [1]
2、变量:
在程序运行期间,随时可能产生一些临时数据,应用程序会将这些数据 保存在一些内存单元中,每个内存单元都用一个标识符来标识,这些内存单元被称为变量。定义的标识符就是变量名,内存单元中存储的数据就是变量的值。
3、StringBuffer 和 StringBuilder 的区别
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
1、String表示字符串类型,属于 引用数据类型,不属于基本数据类型。
2、在java中随便使用 双引号括起来 的都是String对象。
3、java中规定,双引号括起来的字符串,是 不可变 的,也就是说"abc"自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"。
4.字符串常量池
字符串常量池即String Pool 但是jvm中对应的类是StringTable 底层实现为hashtable,hashtable就是一个数组加链表的结构。
下图对应代码 String str1 = “11” 它底层会创建一个String对象实例 然后char会指向oop模型的typeArrayOopDesc(c++类)然后会把这个String封装成一个HashtableEntry它的value就是String的char数组然后把HashtableEntry放入常量池中或者可以理解为底层创建了一个String对象实例 然后又创建了一个HashtableEntry的c++对象 这个HashtableEntry的value会指向String对象实例中char数组然后str1指向这个String对象实例 然后HashtableEntry是放在字符串常量池中 字符串常量池可以理解为是一个list集合

下面的代码对应String str2 = new String(“11”)这里”11”创建了一个char数组并创建了一个String对象实例 然后我们又new了一个String所以我们new的这个String会指向”11”生成的哪个String对象

两个双引号的String 会如果值是一样的会共享

new 两个String也会共享相同的数据

String的常用方法
一.创建String对象
1.常用的两种方式
String s1="11";
String s1= new String("11");
2.String类还有两个较常用的构造方法
(1) String(char a[])用一个字符数组a创建一个String对象。例如:
char[] a={'J','a','v','a'};
String s=new String(a);
上述过程相当于
String s=new String("Java");
(2) String(char a[ ] , int startIndex , int count)提取字符数组a中的一部分字符创建一个String对象,参数startIndex和count分别指定在a中提取字符的起始位置和从该位置开始截取的字符个数
char a[]={'我','们','是','学','生'};
String s=new String(a,2,3);//s=是学生
3.创建字符串数组:
String [] str = new String [20];//20为字符串数组长度
二.String类的常用方法
1、length()方法用来获取一个String对象的字符序列的长度
String str="abccc";
int l=str.length();
2、equals(String s)方法比较当前对象的字符序列是否与参数s指定的String对象的字符序列相同
String str="你";
String str1="好";
String str2="你";
System.out.println(str.equals(str1));//false
System.out.println(str.equals(str2));//true
3、 startsWith() 、 endsWith()判断当前String对象的字符序列前缀或后缀是否是参数指定的String对象s的字符序列
String str="天干物燥,小心火烛";
String str1="天气预报,阴转小雨";
System.out.println(str1.startsWith("天气"));//true
System.out.println(str.startsWith("天气"));//false
System.out.println(str.endsWith("小雨"));//false
System.out.println(str1.endsWith("小雨"));//true
4、compareTo(String s),按字典序与参数指定的String对象s的字符序列比较大小。如果当前String对象的字符序列与s的相同,该方法返回值0;如果大于s的字符序列,该方法返回正值;如果小于s的字符序列,该方法返回负值
String str="abcde";
System.out.println(str.compareTo("boy"));//-1
System.out.println(str.compareTo("aba"));//2
System.out.println(str.compareTo("abcde"));//0
按字典序比较两个String还可以使用compareToIgnoreCase(String s)方法,该方法对比时不区分大小写
5、contains(String s)判断当前String对象的字符序列是否包含参数s的字符序列
String str="abcde";
System.out.println(str.contains("bcd"));//true
System.out.println(str.contains("bcde"));//true
System.out.println(str.contains("bcc"));//false
6、indexOf(String s)和lastIndexOf(String s)从当前String对象的字符序列的0索引位置开始检索首次或最后一次出现s的字符序列的位置,并返回该位置,如果没有检索到,返回值为-1;indexOf(String s,int startpoint)方法是一个重载方法,参数startpoint的值用来指定索引的开始位置
String str="I am a good girl";
System.out.println(str.indexOf("o"));//8
System.out.println(str.lastIndexOf("o"));//9
System.out.println(str.indexOf("good",2));//7
7、substring(int startpoint)复制当前String对象的字符序列中的startpoint位置至最后位置上的字符所得到的字符序列;substring(int start,int end)复制从start位置到end位置的字符序列
String str="abccc";
String s1 = str.substring(1); //返回一个新字符串bccc,内容为指定位置开始到字符串末尾的所有字符
String s2 = str.substring(2, 4);//返回一个新字符串,内容为指定位置开始到指定位置结束所有字符
三.字符串的并置:
String对象可以用’+’进行并置运算,即首尾相接得到一个新的String对象。例如:
String str="你";
String str1="好";
String s;
s=str+str1;
四.字符串拆分与拼接
//String str="nowcoder. a am I";
String[] str1 =str.split(" ");//以空格拆分
StringBuilder str2= new
StringBuilder();//StringBuilder是一个字符拼接的工具类
int j=0;
for(int i=str1.length-1;i>=0;i--){
str2.append(str1[i]+' ');
}
return str2.toString().trim();//toString()转换为字符串, trim()去掉两边的空格
五.字符串与基本数据的相互转化
1、Integer.parseInt(String s)将由“数字”字符组成的字符序列转化为int型数据
int x;
String str="888";
x=Integer.parseInt(str);
2、String.valueOf()将数值转化为String对象
String str=String.valueOf(1234.567);
六.对象的字符串表示
1、toString()获得该对象的字符串表示
String s="adsd";
String str=s.toString();
七.字符串与字符数组与字节数组
1、toCharArray()将字符串转成一个字符数组
String str = "abccc";
char[] chs = str.toCharArray();
2、getChars(int start,int end,char c[],int offset)将当前String对象的一部分字复制到参数c指定的数组中,将字符序列中从start到end-1位置上的字符复制到c中,并从数组c的offset处开始存放这些字符
注意:必须保证数组c能容纳下要被复制的字符
String s="我爱学习lalala";
char[]a=new char[4];
s.getChars(0,4,a,0);
System.out.println(a);//我爱学习
3、String(byte[],int offset,int length)用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节,构造一个String对象
byte d[] = "abcdefgh".getBytes();
System.out.println(d.length);//8
String s=new String(d,6,2);
System.out.println(s);//gh
s=new String(d,0,6);
System.out.println(s);//abcdef
4、Integer.toBinaryString()将字符串转成一个二进制
String str=Integer.toBinaryString(n);//数字转换为二进制字符串
StringBuffer的常用方法
1、append(String s)将String对象s的字符序列追加到当前StringBuffer对象的字符序列中,并返回当前StringBuffer对象的引用
StringBuffer str=new StringBuffer();
str.append("I am a good girl");
System.out.println(str);//I am a good girl
2、StringBuffer append(int n)将int型数据n转化为String对象
StringBuffer str=new StringBuffer();
str.append("888");
System.out.println(str);//888
3、char charAt(int n)得到StringBuffer对象的字符序列位置n上的字符
StringBuffer str=new StringBuffer("I am a good girl");
System.out.println(str.charAt(3));//m
4、setCharAt(int n,char ch)将当前StringBuffer对象的字符序列位置n处的字符用参数ch指定的字符替换
StringBuffer str=new StringBuffer(“I am a good boy”);
str.setCharAt(0,’H’);
System.out.println(str);//H am a good boy
5、insert(int index,String str)将参数str指定的字符序列插入到参数index指定的位置并返回当前对象的引用
StringBuffer str=new StringBuffer("I a good girl");
str.setCharAt(2,'a');
str.setCharAt(3,'m');
System.out.println(str);//I am good girl
6、reverse()将该对象实体中的字符序列翻转,并返回当前对象的引用
StringBuffer str=new StringBuffer("Hello");
System.out.println(str.reverse());//olleH
7、delete(int startIndex,int endIndex)删除一个子字符序列,从startIndex位置到endIndex-1位置处的字符序列被删除
StringBuffer str=new StringBuffer("Habcello");
str.delete(1,4);
System.out.println(str);//Hello
8、replace(int startIndex,int endIndex,String str)将当前StringBuffer对象的字符序列的一个子字符序列用参数str指定的字符序列替换,从startIndex位置到endIndex-1位置处的字符序列被替换
StringBuffer str=new StringBuffer("Hello");
str.replace(1,5,"ahah");
System.out.println(str);//Hahah
9.replace() 起到了一个字符串替换的作用(修改对象中的元素)
实现代码
String s = "We are happy."
a = a.replace(" ","%20");
System.out.println(a);
输出结果:
We%20are%20happy.
空格被替换成了%20
10.copyOfRange(original,int from,int to)该方法返回一个长度为to-from的数组,其中from~min(original.length,to)之间的元素(不包括min(original.length,to))是从数组original复制的元素,剩下的值为0。
int[] str = new int[]{1, 2, 3, 4, 5, 6, 7};
int[] str1 = Arrays.copyOfRange(str, 0, 4);
for (int i = 0; i < str1.length; i++)
System.out.print(str1[i]+" ");//Output 1 2 3 4
String str="I am a girl";
char[] str2=str.toCharArray();
char[] str1 = Arrays.copyOfRange(str2, 0, 4);
for (int i = 0; i < str1.length; i++)
System.out.print(str1[i]);//Output I am
原文链接:https://blog.csdn.net/Piink/article/details/123289914
一、数据类型和‘==’
\1. 基本数据类型(也称原始数据类型): byte,short,char,int,long,float,double,boolean等等
基本数据类型,用“==”比较的时候,比较的就是他们的值
比如 int a = 10; int b = 10; a == b? 10等于10,那么返回的是True
2.引用数据类型: String,类,接口,数组,注解等等
引用数据,用“==”比较的时候,比较的是他们的在内存存放的地址值
二、equals
\1. equals是在Object类(为所有类的父类)中定义了的一种非静态的方法,需要用 对象名.equals(参数) 进行使用,所以equals只能比较引用数据类型,因为引用数据类型才能像类一样实例化出对象来使用方法,而基本数据类型不能进行实例化对象
\2. equals方法在Object类中的源码为: public boolean equals(Object obj) { //this - s1 //obj - s2 return (this == obj);}
由此可知equals默认情况下也是比较对象的内存地址值,所以实际情况下意义不大,一般我们的会对equals进行重写,使它一般用来比较对象的属性值是否相同
\3. 对于引用数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被重写,按照重写的要求来。
三、“==”和equals的区别
\1. 最本质的区别:== 是像 +、-、* 这类的运算符,可直接使用,而equals是一种非静态的方法,需要对象调用
\2. == 既可以比较基本数据类型,也可以比较引用数据类型,比较基本数据类型比较的是值,比较引用数据类型比较的是地址值
\3. equals只能比较引用数据类型,且Object类的equals默认情况下是比较的是地址值,无意义,子类一般会重写,改为比较:属性值
四.ascii码:A,a,0,
A: 65;
a: 97;
0:48
五.Java中的length
一.
Java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性.
java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法.
例子:
1 .所对应的用法:
String[] list={"a","b","c"};
System.out.println(list.length);
2 .所对应的用法
String a="apple";
System.out.println(a.length());
二.
a.length 是求数组的长度。
a[i].length()应该是求二维数组第i行的长度。
六. final和static
static
static是静态的意思,也是全局的意思。static定义的东西,属于全局,与类相关,不与具体实例相关,是类实例之间共享的。
被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个对象来
被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个对象来
static与非static的区别:
在内存中存放的位置不同:所有static修饰的属性和方法都放在内存的方法区(内存的方法区相当于常驻内存,如果一个方法或者变量声明为static,可以节约内存,不必要为每个对象实例化的时候分配内存)里,而非静态的都堆放在堆内存中
生命周期不同:静态在类消失后被销毁,非静态在对象销毁后销毁。
final
final关键字有三个东西可以修饰,修饰非抽象类,修饰非抽象方法,修饰引用。
在类的声明中使用final:
使用了final的类不能再派生子类,就是不可以被继承,简称为断子绝孙类。类中的所有方法都不能被重写。有一些java的面试题问,String可不可以被继承,答案是不可以。因为java.lang.String是一个final类。这可以保证String对象方法的调用确实运行的是String类的方法,而不是经其子类重写后的方法。
在方法的声明中使用final:
被定义为final的方法不能被重写,这个方法成为最终方法,但是该方法仍然可以被继承。如果定义类为final,是所有的方法都不能被重写。而我们只需要类中的几个方法不可以被重写,就在方法前面加上final,而且被定义为final的方法执行效率高。final不能修饰构造方法。
在修饰引用中使用final:
如果引用为基本数据类型,这样变量就是常量了,在程序中这样的变量不可以被修改,修改编译器会报错,而且执行效率比普通的变量要高。final的变量如果没有赋予初值的话,其他方法就必须给他赋值,但只能赋值一次。
如果引用为引用数据类型,比如对象,数组,则该对象、数组本身可以修改,但是指向该对象或数组的地址的引用不能修改。
如果引用的是类的成员变量,则必须当场赋值,否则编译会报错。
static和final一起用:final与static final的区别是:final在一个对象类唯一,static final在多个对象中都唯一;一个既是static又是final的域只占据一段不能改变的存储空间,只有一份。
原文链接:https://blog.csdn.net/dlz_yhn/article/details/124734450
七.Swing界面问题
Java Swing布局管理器
在使用 Swing 向容器添加组件时,需要考虑组件的位置和大小。如果不使用布局管理器,则需要先在纸上画好各个组件的位置并计算组件间的距离,再向容器中添加。这样虽然能够灵活控制组件的位置,实现却非常麻烦。
为了加快开发速度,Java 提供了一些布局管理器,它们可以将组件进行统一管理,这样开发人员就不需要考虑组件是否会重叠等问题。本节介绍 Swing 提供的 6 种布局类型,所有布局都实现 LayoutManager 接口。
边框布局管理器
BorderLayout(边框布局管理器)是 Window、JFrame 和 JDialog 的默认布局管理器。边框布局管理器将窗口分为 5 个区域:North、South、East、West 和 Center。其中,North 表示北,将占据面板的上方;Soufe 表示南,将占据面板的下方;East表示东,将占据面板的右侧;West 表示西,将占据面板的左侧;中间区域 Center 是在东、南、西、北都填满后剩下的区域,如图 1 所示。

图1 边框布局管理器区域划分示意图
提示:边框布局管理器并不要求所有区域都必须有组件,如果四周的区域(North、South、East 和 West 区域)没有组件,则由 Center 区域去补充。如果单个区域中添加的不只一个组件,那么后来添加的组件将覆盖原来的组件,所以,区域中只显示最后添加的一个组件。
BorderLayout 布局管理器的构造方法如下所示。
- BorderLayout():创建一个 Border 布局,组件之间没有间隙。
- BorderLayout(int hgap,int vgap):创建一个 Border 布局,其中 hgap 表示组件之间的横向间隔;vgap 表示组件之间的纵向间隔,单位是像素。
例 1
使用 BorderLayout 将窗口分割为 5 个区域,并在每个区域添加一个标签按钮。实现代码如下:
package ch17;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.*;
public class BorderLayoutDemo
{
public static void main(String[] agrs)
{
JFrame frame=new JFrame("Java第三个GUI程序"); //创建Frame窗口
frame.setSize(400,200);
frame.setLayout(new BorderLayout()); //为Frame窗口设置布局为BorderLayout
JButton button1=new JButton ("上");
JButton button2=new JButton("左");
JButton button3=new JButton("中");
JButton button4=new JButton("右");
JButton button5=new JButton("下");
frame.add(button1,BorderLayout.NORTH);
frame.add(button2,BorderLayout.WEST);
frame.add(button3,BorderLayout.CENTER);
frame.add(button4,BorderLayout.EAST);
frame.add(button5,BorderLayout.SOUTH);
frame.setBounds(300,200,600,300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
在该程序中分别指定了 BorderLayout 布局的东、南、西、北、中间区域中要填充的按钮。该程序的运行结果如图 2 所示。

图2 填充5个区域的效果
如果未指定布局管理器的 NORTH 区域,即将“frame.add(button1,BorderLayout.NORTH);”注释掉,则 WEST、CENTER 和 EAST 3 个区域将会填充 NORTH 区域,如图 3 所示。

图3 缺少NORTH区域
同理,如果未指定布局管理器的 WEST 区域,即将“frame.add(button2,BorderLayout.WEST);”注释掉,则 CENTER 区域将会自动拉伸填充 WEST 区域,如图 4 所示。

图4 缺少WEST区域
流式布局管理器
FlowLayout(流式布局管理器)是 JPanel 和 JApplet 的默认布局管理器。FlowLayout 会将组件按照从上到下、从左到右的放置规律逐行进行定位。与其他布局管理器不同的是,FlowLayout 布局管理器不限制它所管理组件的大小,而是允许它们有自己的最佳大小。
FlowLayout 布局管理器的构造方法如下。
- FlowLayout():创建一个布局管理器,使用默认的居中对齐方式和默认 5 像素的水平和垂直间隔。
- FlowLayout(int align):创建一个布局管理器,使用默认 5 像素的水平和垂直间隔。其中,align 表示组件的对齐方式,对齐的值必须是 FlowLayoutLEFT、FlowLayout.RIGHT 和 FlowLayout.CENTER,指定组件在这一行的位置是居左对齐、居右对齐或居中对齐。
- FlowLayout(int align, int hgap,int vgap):创建一个布局管理器,其中 align 表示组件的对齐方式;hgap 表示组件之间的横向间隔;vgap 表示组件之间的纵向间隔,单位是像素。
例 2
创建一个窗口,设置标题为“Java第四个GUI程序”。使用 FlowLayout 类对窗口进行布局,向容器内添加 9 个按钮,并设置横向和纵向的间隔都为 20 像素。具体实现代码如下:
package ch17;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.*;
public class FlowLayoutDemo
{
public static void main(String[] agrs)
{
JFrame jFrame=new JFrame("Java第四个GUI程序"); //创建Frame窗口
JPanel jPanel=new JPanel(); //创建面板
JButton btn1=new JButton("1"); //创建按钮
JButton btn2=new JButton("2");
JButton btn3=new JButton("3");
JButton btn4=new JButton("4");
JButton btn5=new JButton("5");
JButton btn6=new JButton("6");
JButton btn7=new JButton("7");
JButton btn8=new JButton("8");
JButton btn9=new JButton("9");
jPanel.add(btn1); //面板中添加按钮
jPanel.add(btn2);
jPanel.add(btn3);
jPanel.add(btn4);
jPanel.add(btn5);
jPanel.add(btn6);
jPanel.add(btn7);
jPanel.add(btn8);
jPanel.add(btn9);
//向JPanel添加FlowLayout布局管理器,将组件间的横向和纵向间隙都设置为20像素
jPanel.setLayout(new FlowLayout(FlowLayout.LEADING,20,20));
jPanel.setBackground(Color.gray); //设置背景色
jFrame.add(jPanel); //添加面板到容器
jFrame.setBounds(300,200,300,150); //设置容器的大小
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
上述程序向 JPanel 面板中添加了 9 个按钮,并使用 HowLayout 布局管理器使 9 个按钮间的横向和纵向间隙都为 20 像素。此时这些按钮将在容器上按照从上到下、从左到右的顺序排列,如果一行剩余空间不足容纳组件将会换行显示,最终运行结果如图 5 所示。


图5 FlowLayout布局按钮结果
卡片布局管理器
CardLayout(卡片布局管理器)能够帮助用户实现多个成员共享同一个显示空间,并且一次只显示一个容器组件的内容。
CardLayout 布局管理器将容器分成许多层,每层的显示空间占据整个容器的大小,但是每层只允许放置一个组件。CardLayout 的构造方法如下。
- CardLayout():构造一个新布局,默认间隔为 0。
- CardLayout(int hgap, int vgap):创建布局管理器,并指定组件间的水平间隔(hgap)和垂直间隔(vgap)。
例 3
使用 CardLayout 类对容器内的两个面板进行布局。其中第一个面板上包括三个按钮,第二个面板上包括三个文本框。最后调用 CardLayout 类的 show() 方法显示指定面板的内容,代码如下:
package ch17;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.*;
public class CardLayoutDemo
{
public static void main(String[] agrs)
{
JFrame frame=new JFrame("Java第五个程序"); //创建Frame窗口
JPanel p1=new JPanel(); //面板1
JPanel p2=new JPanel(); //面板2
JPanel cards=new JPanel(new CardLayout()); //卡片式布局的面板
p1.add(new JButton("登录按钮"));
p1.add(new JButton("注册按钮"));
p1.add(new JButton("找回密码按钮"));
p2.add(new JTextField("用户名文本框",20));
p2.add(new JTextField("密码文本框",20));
p2.add(new JTextField("验证码文本框",20));
cards.add(p1,"card1"); //向卡片式布局面板中添加面板1
cards.add(p2,"card2"); //向卡片式布局面板中添加面板2
CardLayout cl=(CardLayout)(cards.getLayout());
cl.show(cards,"card1"); //调用show()方法显示面板2
frame.add(cards);
frame.setBounds(300,200,400,200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
上述代码创建了一个卡片式布局的面板 cards,该面板包含两个大小相同的子面板 p1 和 p2。需要注意的是,在将 p1 和 p2 添加到 cards 面板中时使用了含有两个参数的 add() 方法,该方法的第二个参数用来标识子面板。当需要显示某一个面板时,只需要调用卡片式布局管理器的 show() 方法,并在参数中指定子面板所对应的字符串即可,这里显示的是 p1 面板,运行效果如图 6 所示。

图6 显示p1面板
如果将“cl.show(cards,”card1”)”语句中的 card1 换成 card2,将显示 p2 面板的内容,此时运行结果如图 7 所示。

图7 显示p2面板
网格布局管理器
GridLayout(网格布局管理器)为组件的放置位置提供了更大的灵活性。它将区域分割成行数(rows)和列数(columns)的网格状布局,组件按照由左至右、由上而下的次序排列填充到各个单元格中。
GridLayout 的构造方法如下。
- GridLayout(int rows,int cols):创建一个指定行(rows)和列(cols)的网格布局。布局中所有组件的大小一样,组件之间没有间隔。
- GridLayout(int rows,int cols,int hgap,int vgap):创建一个指定行(rows)和列(cols)的网格布局,并且可以指定组件之间横向(hgap)和纵向(vgap)的间隔,单位是像素。
提示:GridLayout 布局管理器总是忽略组件的最佳大小,而是根据提供的行和列进行平分。该布局管理的所有单元格的宽度和高度都是一样的。
例 4
使用 GridLayout 类的网格布局设计一个简单计算器。代码的实现如下:
package ch17;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.*;
public class GridLayoutDemo
{
public static void main(String[] args)
{
JFrame frame=new JFrame("GridLayou布局计算器");
JPanel panel=new JPanel(); //创建面板
//指定面板的布局为GridLayout,4行4列,间隙为5
panel.setLayout(new GridLayout(4,4,5,5));
panel.add(new JButton("7")); //添加按钮
panel.add(new JButton("8"));
panel.add(new JButton("9"));
panel.add(new JButton("/"));
panel.add(new JButton("4"));
panel.add(new JButton("5"));
panel.add(new JButton("6"));
panel.add(new JButton("*"));
panel.add(new JButton("1"));
panel.add(new JButton("2"));
panel.add(new JButton("3"));
panel.add(new JButton("-"));
panel.add(new JButton("0"));
panel.add(new JButton("."));
panel.add(new JButton("="));
panel.add(new JButton("+"));
frame.add(panel); //添加面板到容器
frame.setBounds(300,200,200,150);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
上述程序设置面板为 4 行 4 列、间隙都为 5 像素的网格布局,在该面板上包含 16 个按钮,其横向和纵向的间隙都为 5。该程序的运行结果如图 8 所示。

图8 计算器
网格包布局管理器
GridBagLayout(网格包布局管理器)是在网格基础上提供复杂的布局,是最灵活、 最复杂的布局管理器。GridBagLayout 不需要组件的尺寸一致,允许组件扩展到多行多列。每个 GridBagLayout 对象都维护了一组动态的矩形网格单元,每个组件占一个或多个单元,所占有的网格单元称为组件的显示区域。
GridBagLayout 所管理的每个组件都与一个 GridBagConstraints 约束类的对象相关。这个约束类对象指定了组件的显示区域在网格中的位置,以及在其显示区域中应该如何摆放组件。除了组件的约束对象,GridBagLayout 还要考虑每个组件的最小和首选尺寸,以确定组件的大小。
为了有效地利用网格包布局管理器,在向容器中添加组件时,必须定制某些组件的相关约束对象。GridBagConstraints 对象的定制是通过下列变量实现的。
\1. gridx 和 gridy
用来指定组件左上角在网格中的行和列。容器中最左边列的 gridx 为 0,最上边行的 gridy 为 0。这两个变量的默认值是 GridBagConstraints.RELATIVE,表示对应的组件将放在前一个组件的右边或下面。
\2. gridwidth 和 gridheight
用来指定组件显示区域所占的列数和行数,以网格单元而不是像素为单位,默认值为 1。
\3. fill
指定组件填充网格的方式,可以是如下值:GridBagConstraints.NONE(默认值)、GridBagConstraints.HORIZONTAL(组件横向充满显示区域,但是不改变组件高度)、GridBagConstraints.VERTICAL(组件纵向充满显示区域,但是不改变组件宽度)以及 GridBagConstraints.BOTH(组件横向、纵向充满其显示区域)。
\4. ipadx 和 ipady
指定组件显示区域的内部填充,即在组件最小尺寸之外需要附加的像素数,默认值为 0。
\5. insets
指定组件显示区域的外部填充,即组件与其显示区域边缘之间的空间,默认组件没有外部填充。
\6. anchor
指定组件在显示区域中的摆放位置。可选值有 GridBagConstraints.CENTER(默认值)、GridBagConstraints.NORTH、GridBagConstraints.
NORTHEAST、GridBagConstraints.EAST、GridBagConstraints.SOUTH、GridBagConstraints.SOUTHEAST、GridBagConstraints.WEST、GridBagConstraints.SOUTHWEST 以及 GridBagConstraints.NORTHWEST。
\7. weightx 和 weighty
用来指定在容器大小改变时,增加或减少的空间如何在组件间分配,默认值为 0,即所有的组件将聚拢在容器的中心,多余的空间将放在容器边缘与网格单元之间。weightx 和 weighty 的取值一般在 0.0 与 1.0 之间,数值大表明组件所在的行或者列将获得更多的空间。
例 5
创建一个窗口,使用 GridBagLayout 进行布局,实现一个简易的手机拨号盘。这里要注意如何控制行内组件的显示方式以及使用 GridBagConstraints.REMAINDER 来控制一行的结束。代码的实现如下:
package ch17;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.*;
public class GridBagLayoutDemo
{
//向JFrame中添加JButton按钮
public static void makeButton(String title,JFrame frame,GridBagLayout gridBagLayout,GridBagConstraints constraints)
{
JButton button=new JButton(title); //创建Button对象
gridBagLayout.setConstraints(button,constraints);
frame.add(button);
}
public static void main(String[] agrs)
{
JFrame frame=new JFrame("拨号盘");
GridBagLayout gbaglayout=new GridBagLayout(); //创建GridBagLayout布局管理器
GridBagConstraints constraints=new GridBagConstraints();
frame.setLayout(gbaglayout); //使用GridBagLayout布局管理器
constraints.fill=GridBagConstraints.BOTH; //组件填充显示区域
constraints.weightx=0.0; //恢复默认值
constraints.gridwidth = GridBagConstraints.REMAINDER; //结束行
JTextField tf=new JTextField("13612345678");
gbaglayout.setConstraints(tf, constraints);
frame.add(tf);
constraints.weightx=0.5; // 指定组件的分配区域
constraints.weighty=0.2;
constraints.gridwidth=1;
makeButton("7",frame,gbaglayout,constraints); //调用方法,添加按钮组件
makeButton("8",frame,gbaglayout,constraints);
constraints.gridwidth=GridBagConstraints.REMAINDER; //结束行
makeButton("9",frame,gbaglayout,constraints);
constraints.gridwidth=1; //重新设置gridwidth的值
makeButton("4",frame,gbaglayout,constraints);
makeButton("5",frame,gbaglayout,constraints);
constraints.gridwidth=GridBagConstraints.REMAINDER;
makeButton("6",frame,gbaglayout,constraints);
constraints.gridwidth=1;
makeButton("1",frame,gbaglayout,constraints);
makeButton("2",frame,gbaglayout,constraints);
constraints.gridwidth=GridBagConstraints.REMAINDER;
makeButton("3",frame,gbaglayout,constraints);
constraints.gridwidth=1;
makeButton("返回",frame,gbaglayout,constraints);
constraints.gridwidth=GridBagConstraints.REMAINDER;
makeButton("拨号",frame,gbaglayout,constraints);
constraints.gridwidth=1;
frame.setBounds(400,400,400,400); //设置容器大小
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
在上述程序中创建了一个 makeButton() 方法,用来将 JButton 组件添加到 JFrame 窗口中。在 main() 方法中分别创建了 GridBagLayout 对象和 GridBagConstraints 对象,然后设置 JFrame 窗口的布局为 GridBagLayout,并设置了 GridBagConstraints 的一些属性。接着将 JTextField 组件添加至窗口中,并通知布局管理器的 GridBagConstraints 信息。
在接下来的代码中,调用 makeButton() 方法向 JFrame 窗口填充按钮,并使用 GridBagConstraints. REMAINDER 来控制结束行。当一行结束后,重新设置 GridBagConstraints 对象的 gridwidth 为 1。最后设置 JFrame 窗口为可见状态,程序运行后的拨号盘效果如图 9 所示。

图9 拨号盘运行效果
盒布局管理器
BoxLayout(盒布局管理器)通常和 Box 容器联合使用,Box 类有以下两个静态方法。
- createHorizontalBox():返回一个 Box 对象,它采用水平 BoxLayout,即 BoxLayout 沿着水平方向放置组件,让组件在容器内从左到右排列。
- createVerticalBox():返回一个 Box 对象,它采用垂直 BoxLayout,即 BoxLayout 沿着垂直方向放置组件,让组件在容器内从上到下进行排列。
Box 还提供了用于决定组件之间间隔的静态方法,如表 1 所示。
| 网格包布局 | 说明 |
|---|---|
| static Component createHorizontalGlue() | 创建一个不可见的、可以被水平拉伸和收缩的组件 |
| static Component createVerticalGlue() | 创建一个不可见的、可以被垂直拉伸和收缩的组件 |
| static Component createHorizontalStrut(int width) | 创建一个不可见的、固定宽度的组件 |
| static Component createVerticalStrut(int height) | 创建一个不可见的、固定高度的组件 |
| static Component createRigidArea(Dimension d) | 创建一个不可见的、总是具有指定大小的组件 |
BoxLayout 类只有一个构造方法,如下所示。
BoxLayout(Container c,int axis)
其中,参数 Container 是一个容器对象,即该布局管理器在哪个容器中使用;第二个参数为 int 型,用来决定容器上的组件水平(X_AXIS)或垂直(Y_AXIS)放置,可以使用 BoxLayout 类访问这两个属性。
例 6
使用 BoxLayout 类对容器内的 4 个按钮进行布局管理,使两个按钮为横向排列,另外两个按钮为纵向排列,代码如下:
package ch17;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.*;
public class BoxLayoutDemo
{
public static void main(String[] agrs)
{
JFrame frame=new JFrame("Java示例程序");
Box b1=Box.createHorizontalBox(); //创建横向Box容器
Box b2=Box.createVerticalBox(); //创建纵向Box容器
frame.add(b1); //将外层横向Box添加进窗体
b1.add(Box.createVerticalStrut(200)); //添加高度为200的垂直框架
b1.add(new JButton("西")); //添加按钮1
b1.add(Box.createHorizontalStrut(140)); //添加长度为140的水平框架
b1.add(new JButton("东")); //添加按钮2
b1.add(Box.createHorizontalGlue()); //添加水平胶水
b1.add(b2); //添加嵌套的纵向Box容器
//添加宽度为100,高度为20的固定区域
b2.add(Box.createRigidArea(new Dimension(100,20)));
b2.add(new JButton("北")); //添加按钮3
b2.add(Box.createVerticalGlue()); //添加垂直组件
b2.add(new JButton("南")); //添加按钮4
b2.add(Box.createVerticalStrut(40)); //添加长度为40的垂直框架
//设置窗口的关闭动作、标题、大小位置以及可见性等
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(100,100,400,200);
frame.setVisible(true);
}
}
在程序中创建了 4 个 JButton 按钮和两个 Box 容器(横向 Box 容器和纵向 Box 容器),并将前两个 JButton 按钮添加到横向 Box 容器中,将后两个 JButton 容器添加到纵向 Box 容器中。程序的运行结果如图 10 所示。

图10 BoxLayout运行结果
提示:使用盒式布局可以像使用流式布局一样简单地将组件安排在一个容器内。包含盒式布局的容器可以嵌套使用,最终达到类似于无序网格布局那样的效果,却不像使用无序网格布局那样麻烦。http://t.csdn.cn/Fy5XO
键盘事件:
键盘事件的事件源一般丐组件相关,当一个组件处于激活状态时,按下、释放或敲击键盘上的某个键时就会发生键盘事件。键盘事件的接口是KeyListener,注册键盘事件监视器的方法是addKeyListener(监视器)。实现KeyListener接口有3个:keyPressed(KeyEvent e):键盘上某个键被按下;keyReleased(KeyEvent e):键盘上某个键被按下,又释放;keyTyped(KeyEvent e):keyPressed和keyReleased两个方法的组合。
管理键盘事件的类是KeyEvent,该类提供方法:
public int getKeyCode(),获得按动的键码getKeyChar()获得字符码,键码表在KeyEvent类中定义,参见附录。
【例 11-20】小应用程序有一个按钮和一个文本区,按钮作为发生键盘事件的事件源,并对它实施监视。程序运行时,先点击按钮,让按钮激活。以后输入英文字母时,在正文区显示输入的字母。字母显示时,字母之间用空格符分隔,且满10个字母时,换行显示。
import java.applet.*
import java.awt.*;
import java.awt.event.*;
public class Example6_10 extends Applet implements KeyListener{
int count =0;
Button button = new Button();
TextArea text = new TextArea(5,20);
public void init(){
button.addKeyListener(this);
add(button);add(text);
}
public void keyPressed(KeyEvent e){
int t = e.getKeyCode();
if(t>=KeyEvent.VK_A&&t<=KeyEvent.VK_Z){
text.append((char)t+" ");
count++;
if(count%10==0)
text.append("\n");
}
}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e){}
}
原文链接:https://blog.csdn.net/softn/article/details/51523326
组合键:
public void keyPressed(KeyEvent kevt) {
if(kevt.getKeyChar()==’c’) {
if(kevt.isAltDown())
//Code if Alt+c pressed
if(kevt.isControlDown())
//Code if Ctrl+c pressed
if(kevt.isShiftDown())
//Code if Shift+c pressed
if(kevt.isAltDown()&&kevt.isControlDown()&&(!kevt.isShiftDown()))
//Code if Alt+Ctrl+c pressed
if(kevt.isAltDown()&&kevt.isShiftDown()&&(!kevt.isControlDown()))
//Code if Alt+Shift+c pressed
if(!(kevt.isAltDown())&&kevt.isControlDown()&&(kevt.isShiftDown()))
//Code if Shift+Ctrl+c pressed
if(kevt.isAltDown()&&kevt.isControlDown()&&kevt.isShiftDown())
//Code if Alt+Ctrl+Shift+c pressed
}
public void keyPressed(KeyEvent key) {
arr[i]=key.getKeyCode();
i++;
if((arr[0]==VK_ALT||arr[1]==VK_ALT||arr[2]==VK_ALT)&& (arr[0]==VK_C||arr[1]==VK_C||arr[2]==VK_C)&&(arr[0]==VK_E||arr[1]==VK_E||arr[2]==VK_E)) {
//Code you want
}
}
https://blog.csdn.net/weixin_39603573/article/details/114053148
八.异常
基础概念以及具体实现
package com.ahut.exception.try_;
public class TryCatchDetail {
public static void main(String[] args) {
/* 1.如果发生异常,则异常后面的代码不会执行,直接进入到catch块。
* 2.如果·异常没有发生,则顺序执行try块,不会进入到catch块
* 3.如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用finally,finally优先执行,
* 然后才会执行,try或catch块里的return或throw语句 */
try {
String str1 = "er";
String str2 = "123";
int a = Integer.parseInt(str1);
System.out.println("数字" + a);
} catch (NumberFormatException e) {
System.out.println("异常信息:" + e.getMessage());
} finally {
System.out.println("finally代码块被执行.....");
}
System.out.println("程序继续运行....");
}
}
package com.ahut.exception.try_;
import com.ahut.exception.NullPointerException_;
public class TryCatchDetail02 {
public static void main(String[] args) {
/* (4).如果try块可能有多个异常,可以使用多个catch捕获,相应处理,但不能同时捕获两个异常。
要求子类异常写在前面,父类异常卸载后面*/
try {
Person person = new Person();
person = null;
System.out.println(person.getName());//NullPointerException
int n1 = 10;
int n2 = 0;
int res = n1 / n2;//ArithmeticException
} catch (NullPointerException e) {
System.out.println("空指针异常:" + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("算数异常:" + e.getMessage());
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
}
}
}
class Person {
private String name = "jack";
public String getName() {
return name;
}
}
package com.ahut.exception;
/* 异常介绍*/
/*●基本概念
Java 语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
●执行过程中所发生的异常事件可分为两大类
1) Error (错误): Java 虚拟机无法解决的严重问题。如: JVM 系统内部错误、资源
耗尽等严重情况。比如: StackOverflowError [栈溢出]和 OOM ( out of
memory ), Error 是严重错误,程序会崩溃。
2) Exception :其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针
对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等, Exception 分为两大类:运行时异常]和编译时异常[]。*/
public class Exception01 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;//ctrl + alt 代码格式化,即自动对=对齐
//为了不让程序崩溃,所以设计了异常处理机制
//将代码块选中->ctrl + alt + t ->选中太try-catch
//如果没有try-catch和throw那么默认使用throw
try {
int res = num1 / num2;//抛出异常,ArithmeticException
} catch (Exception e) {
//throw new RuntimeException(e);
e.printStackTrace();//打印异常栈信息
System.out.println(e.getMessage());//输出异常信息,继续运行
}
System.out.println("程序继续运行");
}
}
/*异常体系图的小结
1.异常分为两大类,运行时异常和编译时异常
2.运行时异常,编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。
java.lang.RuntimeException 类及它的子类都是运行时异常
3.对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
4.编译时异常,是编译器要求必须处置的异常。*/
常见异常:
一、空指针异常:java.lang.NullPointerException
java中的异常类,当应用程序调用某对象结果为空时,抛出该异常。
二、类型转换异常:java.lang.ClassCastException
字面意思,类型不匹配进行转换时所抛出的异常,通常在“向下转型”(父类型转换为子类型,强制类型转换)时发生;
解决方法,在需要使用强制类型转换的场景下,添加instanceof运算符,判断左右操作数是否具有继承或实现关系,返回为true时可以进行类型转换。
三、数组下标越界异常:ArrayIndexOutOfBoundsException
数组的下标从0开始计数,当遍历数据超出数组长度-1的情况下会抛出数组下标越界异常。
四、数字格式化异常:NumberFormatException
// 非数字型不可包装为Integer
Integer a= new Integer("中文");//抛出异常
Integer a= new Integer("123");//正常包装
五、数字异常:ArithmeticException
除数为0的时候
六、关于数据库访问错误或其他错误的异常:SQLException(编译时会被发现)
七、找不到文件:FileNotFoundException(编译时会被发现)
八、当发生某种I/O异常时:IOException(编译时会被发现)
九、向方法传递了一个不合法或者不正确的参数(非法传参异常):IllegalArgumentException
十、当程序运行被打断时抛出的异常:InterruptedException
可以打断程序运行的一些方法:
java.lang.Object类的wait()方法;
java.lang.Thread类的sleep()方法;
java.lang.Thread类的Join()方法;
原文链接:https://blog.csdn.net/pxsdbzxj/article/details/127836788
九.Java的读写方式——IO流读写
1.IO流,什么是IO?
I : Input
O: Output
通过IO可以完成硬盘文件的读和写。
2.IO流的分类?
有多种分类方式:
1)一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫做输入。或者叫做读(Read)。
从内存中出来,叫做输出。或者叫做写(Write)。
2)另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节byte等同于一次读取8个二进制位。
这种流是万能的,什么类型的文件都可以读取。包括文本文件、图片、声音文件、视频文件等等…
假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:1个字节,正好读到’a’
第二次读:1个字节,正好读到’中’字符的一半
第三次都:1个字节,正好读到’中’字符的另外一半
有的流是按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件存在的。这种流不能读取:图片、声音、视频等文件,只能读取纯文本文件,连word文件都无法读取。
假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读: ‘a’字符(’a’字符在windows系统中占用1个字节)
第二次读: ‘中’字符(’中’字符在windows系统中占用2个字节)
综上所述: 流的分类
输入流、输出流(按照流的方向进行分类)
字节流、字符流(按照读取的方式进行分类)
3.IO流都在java.io.*包下
java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握,在java中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些?
java中所有的流都是在: java.io.*下。
java中主要还是研究:
怎么new流对象。
调用流对象的哪个方法是读,哪个方法是写。
4.java IO流有四个家族
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
注意: 在java中只要”类名”以Stream结尾的都是字节流。以”Reader/Writer”结尾的都是字符流。
5.java.io包下需要掌握的流有16个
文件专属:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DateInputStream
java.io.DateOutputStream
标准输出流:
java.io.printWriter
java.io.printStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
6.FileInputStream的1个字节读入法
package com.jmpower.javase.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
java.io.FileInputStream:
1.文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
2.字节的方式,完成输入的操作,完成读的操作。(硬盘-->内存)
*/
public class FileInputStreamTest01 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
//创建字节输入流对象
//文件路径:C:\Users\Jm\Desktop\Java\doSome.txt
//文件内容为:abcedf
//FileInputStream fis=new FileInputStream("C:\\Users\\Jm\\Desktop\\Java\\doSome.txt");
//都采用了: 绝对路径
//将"\\"写成"/"也是可以的
fis=new FileInputStream("C:/Users/Jm/Desktop/Java/doSome.txt");
int readDate=fis.read();//这个方法的返回值是读到字节本身
System.out.println(readDate);// 97
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//在finally语句块中确保流一定要关闭
if (fis != null) {
//关闭流的前提是:流不是空。流是null的时候没必要关闭。
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
7.FileInputStream的byte[]读入法
package com.jmpower.javase.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
int read(byte[] b)
一次最多读取b.length个字节。
减少硬盘和内存之间的交互,提高程序的执行效率。
往byte[]数组当中读。
*/
public class FileInputStreamTest02 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
//相对路径的话呢?相对路径一定是从当前所在的位置作为起点开始找!
//IDEA默认的当前路径是工程Object的根就是IDEA的默认当前路径。
//文件内容:abcdef
fis=new FileInputStream("text02.txt");
byte[] bytes=new byte[4];//准备一个4个长度的byte数组,一次最多读取4个字节。
//这个方法的返回值是,读取到的字节数量。(不是字节本身)
//int readCount=fis.read(bytes);
//System.out.println(readCount);// 第一次读到4个字节
//将字节数组全部转换成字符串
//System.out.println(new String(bytes));
//不应该全部转换,应该读取了多少个字节,转换多少个。
//System.out.println(new String(bytes,0,readCount));// abcd
//最终版本,读取文件
int readCount=0;
while((readCount = fis.read(bytes))!=-1)
{
System.out.print(new String(bytes,0,readCount));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
8.FileInputStream的其他方法
1)int available(): 返回流当中剩余的没有读取到的字节的数量
package com.jmpower.javase.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest03 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis=new FileInputStream("test03");
System.out.println("总字节数量:"+fis.available());// 41
//读一个字节
//int readDate=fis.read();
//还剩下的可读字节为40
//System.out.println("还剩下的可读字节的数量:"+fis.available());// 40
//这个方法有什么用?
byte[] bytes=new byte[fis.available()];
//不需要循环了!
//直接读一次就行了。
int readCount=fis.read(bytes);// 6
System.out.println(new String(bytes)); // abcdef
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2)long stip(long n): 跳过几个字节不读
package com.jmpower.javase.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest04 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis=new FileInputStream("text02.txt");
//a b c d e f
//97 98 99 100 101 102
System.out.println(fis.read());// 97
fis.skip(3);//跳过三个字节
System.out.println(fis.read());// 101
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
9.FileOutputStream用法
package com.jmpower.javase.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest01 {
public static void main(String[] args) {
FileOutputStream fos=null;
try {
//file01文件不存在会自动新建!
//这种方式慎用,这种方式会先将文件清空,然后重新写入。
//fos=new FileOutputStream("file01");
//以追加的方式在文件末尾写入。不会清空原文件的内容。
fos=new FileOutputStream("text02.txt",true);
//开始写
byte[] bytes={97,98,99,100};
//将byte数组全部写出!
fos.write(bytes);// abcd
//将bute数组的一部分写出!
fos.write(bytes,0,2);// ab
//字符串
String s="我是中国人!";
//将字符串转换成数组
byte[] bs=s.getBytes();
//写
fos.write(bs);
//写完之后一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
10.文件复制
package com.jmpower.javase.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
使用FileInputStream + FileOutputStream完成文件的拷贝。
拷贝的过程一定是一边读,一边写。
使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都可以拷贝。
*/
public class Copy01 {
public static void main(String[] args) {
FileInputStream fis=null;
FileOutputStream fos=null;
try {
//创建一个输入流对象
fis=new FileInputStream("C:\\Users\\Jm\\Pictures\\自建\\十三届蓝桥杯省一.jpg");
//创建一个输出流对象
fos=new FileOutputStream("C:\\Users\\Jm\\Desktop\\Java\\十三届蓝桥杯省一.jpg");
//最核心的: 一边读,一边写
byte[] bytes=new byte[1024*1024];//1MB(一次最多拷贝1MB)
int readCount=0;
while((readCount=fis.read(bytes))!=-1)
{
fos.write(bytes,0,readCount);
}
//刷新,输出流最后要刷新
fos.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//分开try,不要一起try
//一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
11.FileReader用法
package com.jmpower.javase.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
FileReader:
读取文本内容时,比较方便,快捷。
一次读取一个字符。
*/
public class FileReaderTest {
public static void main(String[] args) {
FileReader reader=null;
try {
//创建文件字符输入流
reader=new FileReader("text02.txt");
//开始读
char[] chars=new char[4];//一次读取4个字符(1个字符2个字节)
int readCount=0;
while((readCount=reader.read(chars))!=-1)
{
System.out.print(new String(chars,0,readCount));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
12.复制普通文本文件(Reader/Writer)
package com.jmpower.javase.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Copy02 {
public static void main(String[] args) {
FileReader in=null;
FileWriter out=null;
try {
//读
in=new FileReader("src/com/jmpower/javase/io/FileInputStreamTest02.java");
//写
out=new FileWriter("reader");
//一边读一边写
char[] chars=new char[1024*512];//1MB
int readCount=0;
while((readCount=in.read(chars))!=-1)
{
out.write(new String(chars,0,readCount));
}
//刷新
out.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭流
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
13.带有缓冲区的字符输入流
package com.jmpower.javase.io;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/*
BufferedReader:
带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
*/
public class BufferedReaderTest01 {
public static void main(String[] args) {
FileReader reader=null;
BufferedReader br=null;
try {
reader=new FileReader("src/com/jmpower/javase/io/Copy02.java");
//当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做: 节点流。
//外部负责包装的这个流,叫做: 包装流,还有一个名字叫做: 处理流。
//像当前这个程序来说: FileReader叫做一个节点流。BufferedReader就是包装流/处理流。
br=new BufferedReader(reader);
/*//第一行
String firstLine=br.readLine();
System.out.println(firstLine);
//第二行
String secondLine=br.readLine();
System.out.println(secondLine);*/
//br.readLine()方法读取一个文本行,但不带换行符。
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭流
//对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码)
if (br != null) {
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
14.包装流和节点流(即转换流)
package com.jmpower.javase.io;
import java.io.*;
public class BufferedReaderTest02 {
public static void main(String[] args) {
BufferedReader br=null;
try {
//字节流
FileInputStream in=new FileInputStream("src/com/jmpower/javase/io/Copy02.java");
//通过转换流转换(InputStreamReader将字节流转换成字符流)
//in是节点流。reader是包装流。
InputStreamReader reader=new InputStreamReader(in);
//这个构造方法只能传一个字符流。不能传字节流。
//reader是节点流。br是包装流。
br=new BufferedReader(reader);
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭最外层
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
15.带有缓冲区的字符输出流
package com.jmpower.javase.io;
import java.io.*;
/*
BufferedWriter: 带有缓冲的字符输出流
OutputStreamWriter: 转换流
*/
public class BufferedWriterTest01 {
public static void main(String[] args) {
BufferedWriter out=null;
try {
//带有缓冲的字符输出流
//out=new BufferedWriter(new FileWriter("bo"));
out=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("doSome"/*,true*/)));
//开始写
out.write("123");
out.write("\n");
out.write("456");
//刷新(输出流记得刷新)
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
16.数据流
1)写
package com.jmpower.javase.io;
import java.io.*;
/*
java.io.DataOutputStream:数据专属的流
这个流可以将数据连通数据的类型一并写入文件。
注意: 这个文件不是普通文本文档。(这个文件使用记事本打不开)
*/
public class DataOutputStreamTest01 {
public static void main(String[] args) {
DataOutputStream dos=null;
try {
//创建数据专属的字节输出流
dos=new DataOutputStream(new FileOutputStream("data"));
//写数据
byte b=100;
short s=400;
int i=200;
long l=300;
float f=3.0f;
double d=3.14;
boolean sex=false;
char c='0';
//写
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
//刷新
dos.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2)读
package com.jmpower.javase.io;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
java.io.DataInputStream: 数据字节输入流
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和谐的顺序一致。才可以正常读出数据。
*/
public class DataInputStreamTest01 {
public static void main(String[] args) {
DataInputStream dis=null;
try {
dis=new DataInputStream(new FileInputStream("data"));
//开始读
byte b=dis.readByte();
short s=dis.readShort();
int i=dis.readInt();
long l=dis.readLong();
float f= dis.readFloat();
double d= dis.readDouble();
boolean sex= dis.readBoolean();
char c= dis.readChar();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(sex);
System.out.println(c);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
17.标准输出流
package com.jmpower.javase.io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/*
java.io.PrintStream: 标准的字节输出流。默认输出到控制台。
*/
public class PrintStreamTest {
public static void main(String[] args) {
//联合起来写
System.out.println("hello world!");
//分开写
java.io.PrintStream ps=System.out;
ps.println("hello zhangsan");
ps.println("hello lisi");
ps.println("hello wangwu");
//标准输出流不需要手动colse()关闭。
//可以改变标准输出流的输出方向吗?可以
try {
//标准输出流不再指向控制台,指向“log”文件
PrintStream printStream=new PrintStream(new FileOutputStream("log"));
//修改输出方向,将输出方向修改到"log"文件
System.setOut(printStream);
//再输出,输入到了"log"文件中
System.out.println("hello lisi");
System.out.println("hello zhangsan");
System.out.println("hello wangwu");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
原文链接:https://blog.csdn.net/m0_66689823/article/details/125881759
18.read,readline,writer,newline
read()方法读取输入流
read()方法读取的是单个字符,也可以说只要有输入流就会一直读取,返回的值是:作为一个整数(其范围从 0 到 65535 (0x00-0xffff))读入的字符,如果已到达流末尾,则返回 -1
因此使用该方法时应该尽量避免输入流数据重复的发送,这样会导致read()读取到很长的重复数据,导致读取超时,但是对于仅有单次发送的输入流,则可以使用read()方法,具体使用如下:
InputStream is = client.getInputStream(); //获取到客户端的输入流
byte[] b = new byte[1024]; //定义字节数组
int len = is.read(b); //由于信息的传输是以二进制的形式,所以要以二进制的形式进行数据的读取
String data = new String(b, 0,len);
System.out.println("输入流消息:" + data);
Readline()方法读取输入流
readLine方法读取一个是文本行。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。 返回的值是:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
也就是说只要readLine方法读取到的输入流中含有回车换行符,则读取结束,这样的话对于连续重复发送的输入流信息,我们可以在每条信息的末尾增加一个回车换行符,这样readLine方法读取到该符号时就会自动结束,
具体使用如下:
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "gb2312"));
String msg = br.readLine();
if(msg != null) {
System.out.println("收到输入流信息:" + msg + "\n");
}
原文链接:https://blog.csdn.net/weixin_44985880/article/details/109729502
Writer类的构造函数
创建一个字符流writer,将同步 writer 自身
protected Writer()
创建一个字符流 writer,将同步给定的对象
protected Writer(Object lock)
Writer类的方法
Writer append(char c) 将指定字符添加到此 writer
Writer append(CharSequence csq) 将指定字符序列添加到此 writer
Writer append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer.Appendable
abstract void close() 关闭此流,但要先刷新它
abstract void flush() 刷新该流的缓冲
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分
void write(int c) 写入单个字符
void write(String str) 写入字符串
void write(String str, int off, int len) 写入字符串的某一部分
Writer类图
Writer常见的实现子类
BufferedWriter 字符缓冲输出流
FileWriter 用来写入字符串到文件
OutputStreamWriter 写入字符,同时可以设置编码集
例
public class TestClass{
public static void main(String args[]) throws Exception{
//使用File类找到一个文件
File f= new File("d:" + File.separator + "java265.txt") ;
//通过子类实例化父类对象
Writer out = null ; // 准备好一个输出的对象
out = new FileWriter(f) ; // 通过对象多态性,进行实例化
// 进行写操作
String str = "java265.com 最好的java网站" ; // 准备一个字符串
out.write(str) ; // 将内容输出,保存文件
//关闭输出流
out.close() ; // 关闭输出流
}
};
原文链接:https://blog.csdn.net/qq_25073223/article/details/126259445
newline
[英]Put the line separator String onto the print stream.
[中]将行分隔符字符串放入打印流。
newLine()与\r\n的区别
newLine()是跨平台的方法
\r\n只支持的是windows系统
补充:
读取控制台输入
Java 的控制台输入由 System.in 完成。
为了获得一个绑定到控制台的字符流,你可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。
下面是创建 BufferedReader 的基本语法:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符,或者用 readLine() 方法读取一个字符串。
从控制台读取多字符输入
从 BufferedReader 对象读取一个字符要使用 read() 方法,它的语法如下:
int read( ) throws IOException
每次调用 read() 方法,它从输入流读取一个字符并把该字符作为整数值返回。 当流结束的时候返回 -1。该方法抛出 IOException。
下面的程序示范了用 read() 方法从控制台不断读取字符直到用户输入 q。
BRRead.java 文件代码:
//使用 BufferedReader 在控制台读取字符
import java.io.*;
public class BRRead {
public static void main(String[] args) throws IOException {
char c;
// 使用 System.in 创建 BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println(“输入字符, 按下 ‘q’ 键退出。”);
// 读取字符
do {
c = (char) br.read();
System.out.println(c);
} while (c != ‘q’); } }
十.多线程
1 - 线程
1.1 - 进程
进程就是正在运行中的程序(进程是驻留在内存中的)
是系统执行资源分配和调度的独立单位
每一进程都有属于自己的存储空间和系统资源
注意:进程A和进程B的内存独立不共享。
1.2 - 线程
线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径
单线程:一个进程中包含一个顺序控制流(一条执行路径)
多线程:一个进程中包含多个顺序控制流(多条执行路径)
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
对于单核的CPU来说,不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是多个事情同时在做。

1.3 -java中多线程的实现原理

就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权力(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态。
运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法接着上一次的代码继续往下执行。
阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。之前的时间片没了需要再次回到就绪状态抢夺CPU时间片。
锁池:在这里找共享对象的对象锁线程进入锁池找共享对象的对象锁的时候,会释放之前占有CPU时间片,有可能找到了,有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片。(这个进入锁池,可以理解为一种阻塞状态)
1.4 - 多线程的实现方式(一)
继承Thread类
1、自定义一个类MyThread类,用来继承与Thread类
2、在MyThread类中重写run()方法
3、在测试类中创建MyThread类的对象
4、启动线程
* ```java
/**
* @author Mr.乐
* @Description
*/
public class Demo01 {
public static void main(String[] args) {
//创建线程
MyThread t01 = new MyThread();
MyThread t02 = new MyThread();
MyThread t03 = new MyThread("线程03");
//开启线程
// t01.run();
// t02.run();
// t03.run();
// 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
// start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
// 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
// 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
// run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
t01.start();
t02.start();
t03.start();
//设置线程名(补救的设置线程名的方式)
t01.setName("线程01");
t02.setName("线程02");
//设置主线程名称
Thread.currentThread().setName("主线程");
for (int i = 0; i < 50; i++) {
//Thread.currentThread() 获取当前正在执行线程的对象
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
//run方法是每个线程运行过程中都必须执行的方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
```
此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
单纯使用run()方法是不能多线程并发的。
1.5 - 设置和获取线程名
设置线程名
setName(String name):设置线程名
通过带参构造方法设置线程名
获取线程名
getName():返回字符串形式的线程名
Thread.CurrentThread():返回当前正在执行的线程对象
1.6 - 多线程的实现方式(二)
实现Runnable接口
1、自定义一个MyRunnable类来实现Runnable接口
2、在MyRunnable类中重写run()方法
3、创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
4、启动线程
* ```java
/**
* @author Mr.乐
* @Description
*/
public class Demo02 {
public static void main(String[] args) {
MyRunnable myRun = new MyRunnable();//将一个任务提取出来,让多个线程共同去执行
//封装线程对象
Thread t01 = new Thread(myRun, "线程01");
Thread t02 = new Thread(myRun, "线程02");
Thread t03 = new Thread(myRun, "线程03");
//开启线程
t01.start();
t02.start();
t03.start();
//通过匿名内部类的方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
},"线程04").start();
}
}
//自定义线程类,实现Runnable接口
//这并不是一个线程类,是一个可运行的类,它还不是一个线程。
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
```
###### 1.7 - 多线程的实现方式(三)
实现Callable接口( java.util.concurrent.FutureTask; /JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。)
1、自定义一个MyCallable类来实现Callable接口
2、在MyCallable类中重写call()方法
3、创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。
4、启动线程
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author Mr.乐
* @Description 线程实现的第三种方式
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
1.8 -线程控制
方法名 说明
void yield() 使当前线程让步,重新回到争夺CPU执行权的队列中
static void sleep(long ms) 使当前正在执行的线程停留指定的毫秒数
void join() 等死(等待当前线程销毁后,再继续执行其它的线程)
void interrupt() 终止线程睡眠
1.8.1 -sleep()方法 (谁执行谁就是当前线程)
/**
* @author Mr.乐
* @Description 线程睡眠
*/
public class DemoSleep {
public static void main(String[] args) {
// 创建线程
MyThread1 t01 = new MyThread1("黄固");
MyThread1 t02 = new MyThread1("欧阳锋");
MyThread1 t03 = new MyThread1("段智兴");
MyThread1 t04 = new MyThread1("洪七公");
//开启线程
t01.start();
t02.start();
t03.start();
t04.start();
}
}
class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(String name) {
super(name);
}
@Override
// 重点:run()当中的异常不能throws,只能try catch
// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
public void run() {
for (int i = 1; i < 50; i++) {
System.out.println(this.getName() + "正在打出第 - " + i + "招");
try {
Thread.sleep(500);//让当前正在执行的线程睡眠指定毫秒数
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:run()方法中的异常只能try catch,因为父类没有抛出异常,子类不能抛出比父类更多的异常。
1.8.2 -interrupt()方法和stop()方法
/**
* @author Mr.乐
* @Description 终止线程
*/
public class DemoInterrupt {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName(“t”);
t.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
t.interrupt();
// t.stop(); //强行终止线程
//缺点:容易损坏数据 线程没有保存的数据容易丢失
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + “—> begin”);
try {
// 睡眠1年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// e.printStackTrace();
}
//1年之后才会执行这里
System.out.println(Thread.currentThread().getName() + “—> end”);
}
}
1.8.3 -合理的终止线程
做一个boolean类型的标记
/**
* @author Mr.乐
* @Description
*/
public class DemoSleep02 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.setName(“t”);
t.start();
// 模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止线程
// 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
r.run = false;
}
}
class MyRunable4 implements Runnable {
// 打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// return就结束了,你在结束之前还有什么没保存的。
// 在这里可以保存呀。
//save....
//终止当前线程
return;
}
}
}
}
1.8.4 - yield()
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
/**
* @author Mr.乐
* @Description 线程让位
*/
public class DemoYield {
public static void main(String[] args) {
//创建线程
MyThread5 t01 = new MyThread5(“线程01”);
MyThread5 t02 = new MyThread5(“线程02”);
MyThread5 t03 = new MyThread5(“线程03”);
//开启线程
t01.start();
t02.start();
t03.start();
}
}
class MyThread5 extends Thread{
public MyThread5() {
}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(30 == i){
Thread.yield();//当循i环到30时,让线程让步
//1、回到抢占队列中,又争夺到了执行权
//2、回到抢占队列中,没有争夺到执行权
}
System.out.println(this.getName() + ":" + i);
}
}
}
1.8.5 -join()
1.9 - 线程的调度
线程调度模型
均分式调度模型:所有的线程轮流使用CPU的使用权,平均分配给每一个线程占用CPU的时间。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程来执行,优先级高的占用CPU时间相对来说会高一点点。
Java中JVM使用的就是抢占式调度模型
getPriority():获取线程优先级
setPriority:设置线程优先级
/**
* @author Mr.乐
* @Description 线程的调度
*/
public class Demo07 {
public static void main(String[] args) {
//创建线程
MyThread t01 = new MyThread(“线程01”);
MyThread t02 = new MyThread(“线程02”);
MyThread t03 = new MyThread(“线程03”);
//获取线程优先级,默认是5
// System.out.println(t01.getPriority());
// System.out.println(t02.getPriority());
// System.out.println(t03.getPriority());
//设置线程优先级
t01.setPriority(Thread.MIN_PRIORITY); //低 - 理论上来讲,最后完成
t02.setPriority(Thread.NORM_PRIORITY); //中
t03.setPriority(Thread.MAX_PRIORITY); //高 - 理论上来讲,最先完成
//开启线程
t01.start();
t02.start();
t03.start();
}
}
2 - 线程的安全
2.1 - 数据安全问题
是否具备多线程的环境
是否有共享数据
是否有多条语句操作共享数据
例如:我和小明同时取一个账户的钱,我取钱后数据还没返回给服务器,小明又取了,这个时候小明的余额还是原来的。
如何解决?线程排队执行(不能并发),线程同步机制。
2.1.1 -变量对线程安全的影响
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
2.1.2 -模拟线程安全问题
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account(“act-001”, 10000);
// 创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 设置name
t1.setName(“t1”);
t2.setName(“t2”);
// 启动线程取款
t1.start();
t2.start();
//t1对act-001取款5000.0成功,余额5000.0
//t2对act-001取款5000.0成功,余额5000.0
}
}
public class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的执行表示取款操作。
// 假设取款5000
double money = 5000;
// 取款
// 多线程并发执行这个方法。
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
}
/**
* @author Mr.乐
* @Description
*/
public class Account {
// 账号
private String actno;
// 余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
public void withdraw(double money){
// t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
// 取款之前的余额
double before = this.getBalance(); // 10000
// 取款之后的余额
double after = before - money;
// 在这里模拟一下网络延迟,100%会出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
// 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
this.setBalance(after);
}
}
2.2 - 线程同步的利弊
好处:解决了线程同步的数据安全问题
弊端:当线程很多的时候,每个线程都会去判断同步上面的这个锁,很耗费资源,降低效率
2.3 -编程模型
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,
谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。
2.4 -线程同步
2.4.1 -线程同步方式
同步语句块:synchronized(this){方法体} (synchronized括号后的数据必须是多线程共享的数据,才能达到多线程排队)
// 以下代码的执行原理?
// 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
// 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
// 找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
// 占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
// 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
// 共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
// 直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
// t2占有这把锁之后,进入同步代码块执行程序。
//
// 这样就达到了线程排队执行。
// 这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
// 执行的这些线程对象所共享的。
synchronized (this){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
普通同步方法:修饰符 synchronized 返回值类型 方法名(形参列表){方法体}
synchronized出现在实例方法上,一定锁的是this(此方法)。不能是其他的对象了。 所以这种方式不灵活。
另外还有一个缺点:synchronized出现在实例方法上, 表示整个方法体都需要同步,可能会无故扩大同步的 范围,导致程序的执行效率降低。所以这种方式不常用。
public synchronized void withdraw(double money){
double before = this.getBalance(); // 10000
// 取款之后的余额
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
this.setBalance(after);
静态同步方法:修饰符 synchronized static 返回值类型 方法名(形参列表){方法体}
(静态方法中不能使用this)表示找类锁。类锁永远只有1把。
2.5 -如何解决线程安全问题
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。
2.6 -Lock
应用场景不同,不一定要在同一个方法中进行解锁,如果在当前的方法体内部没有满足解锁需求时,可以将lock引用传递到下一个方法中,当满足解锁需求时进行解锁操作,方法比较灵活。
private Lock lock = new ReentrantLock();//定义Lock类型的锁
public void withdraw(double money){
// t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
// 取款之前的余额
lock.lock();//上锁
double before = this.getBalance(); // 10000
// 取款之后的余额
double after = before - money;
// 在这里模拟一下网络延迟,100%会出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
// 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
this.setBalance(after);
lock.unlock();//解锁
}
2.7 -死锁
形成原因
当两个线程或者多个线程互相锁定的情况就叫死锁
避免死锁的原则
顺序上锁,反向解锁,不要回头
/**
* @author Mr.乐
* @Description 死锁
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2两个线程共享o1,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
2.8 -守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,
守护线程自动结束。
注意:主线程main方法是一个用户线程。
守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那里看着,每到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。
public class Demo09 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName(“备份数据的线程”);
// 启动线程之前,将线程设置为守护线程
t.setDaemon(true);
t.start();
// 主线程:主线程是用户线程
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread {
public void run(){
int i = 0;
// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
while(true){
System.out.println(Thread.currentThread().getName() + “—>” + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3 -定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持
定时任务的。
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Mr.乐
* @Description 定时类
*/
public class DemoTimer {
public static void main(String[] args) {
Timer timer = new Timer();//创建Timer定时器类的对象
//匿名内部类
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我被执行了!~");
System.gc();//告诉JVM运行完毕,可以把我回收
}
},5000);
}
}
3.1 -线程与定时器执行轨迹不同
线程与定时器之间互不抢占CPU时间片
* ```java
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Mr.乐
* @Description 线程与定时器的执行轨迹不同
*/
public class DemoTimer {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "<--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//定时器实现
new Timer().schedule(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.gc();//将编程垃圾的定时器进行回收
}
},5000);
}
}
```
原文链接:https://blog.csdn.net/zdl66/article/details/126297036
1.7一些常用方法
start()
start() 函数 API 定义:
使该线程开始执行,Java 虚拟机调用该线程的 run 方法。结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
start() 函数启动线程执行以下任务:
- 它统计了一个新线程
- 线程从 New State 移动到 Runnable 状态。
- 当线程有机会执行时,它的目标
run()方法将运行。- start() 方法不能多次重复调用,否则抛出 java.lang.IllegalStateException 异常;
小结:
start() 函数用来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码;通过调用 Thread 类的 start() 方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此 Thread 类调用方法 run() 来完成其运行操作的, 这里方法 run() 称为线程体,它包含了要执行的这个线程的内容。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
run()
run() 函数 API 定义:
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。Thread 的子类应该重写该方法。
run() 函数启动:
线程类的
run()方法是 Runnable 接口的一个抽象函数,由 java 虚拟机直接调用的,不会创建的新线程。所以可以被多次调用,因为它只是一个抽象函数。
小结:
run() 函数只是类的一个普通函数而已,如果直接调用 run 方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run 方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
作者:不愿意透露姓名的轩叔叔
链接:https://www.jianshu.com/p/76933cd66c2f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
总结:调用 start 方法方可启动线程,而 run 方法只是 Thread 的一个普通方法调用,还是在主线程里执行。
Thread类中run()和start()方法的区别如下:
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
sleep() 的作用
是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
(1)wait:线程不再进行活动,不再参与调度,进入WAITING状态,因此不会浪费CPU资源,也不会去竞争锁资源。它需要等待其它线程执行一个特殊动作,也就是“notify(通知)”在这个对象上等待的线程从WAITING状态中释放出来,重新进入调度队列(ready queue)中。
(2)notify:则选取所通知对象的 wait set 中的一个线程释放;
(3)notifyAll:则释放所通知对象的 wait set 上的全部线程。
原文链接:https://blog.csdn.net/qq_33479841/article/details/125596223
join
先将当前线程挂起,待其他线程结束后在执行当前线程的代码;
2、锁
在java中,加锁需要使用synchronized关键字,锁的本质就是对于共享资源访问的一个限制,它让同一时间内只有一个线程能访问这个共享资源,以此确保多线程并发的原子性操作,因此对于synchronized而言,加锁是有作用范围的,范围就是共享资源的使用范围。
2.1、实例锁
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁,只针对于当前对象实例有效。
public class SynchronizedDemo {
synchronized void method1() {
}
void method2() {
synchronized (this) {
}
}
}
2.2、类锁
静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁,针对所有对象都互斥,因为静态方法是唯一的,所以在静态方法上加锁也是类锁。
public class SynchronizedDemo {
synchronized static void method3() {
}
void method4() {
synchronized (SynchronizedDemo.class) {
}
}
}
2.3、代码块
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁,如单例模式给HashMap加锁。
public class SynchronizedDemo {
Object object = new Object();
void method5() {
synchronized (object) {
}
}
}
————————————————
版权声明:本文为CSDN博主「烟锁迷城」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiayibingdong/article/details/124388116
十一.socke编程
1.1 网络架构模型 网络架构模型主要有OSI参考模型和TCP/IP五层模型
1.1.1 OSI参考模型
OSI(Open System Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。ISO为了更好的使网络应用更为普及,推出了OSI参考模型,这样所有的公司都按照统一的标准来指定自己的网络,就可以互通互联了。
OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)。
1.1.2 TCP/IP五层模型
TCP/IP五层协议(物理层、数据链路层、网络层、传输层、应用层)
1.1.3 各协议层的说明
应用层
应用层最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,TELNET等。
传输层
建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
网络层
本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。
1.2 网络编程中的问题
常见的网络编程中的问题主要是怎么定位网络上的一台主机或多台主机,另一个是定位后如何进行数据的传输。对于前者,在网络层中主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。对于后者,在传输层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制。
对于客户端/服务器(C/S)结构。 即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。
对于浏览器/服务器(B/S)结构。 客户则在需要服务时向服务器进行请求。服务器响应后及时返回,不需要实时监听端口。
1.3 TCP协议与UDP协议
1.3.1 TCP
TCP是(Tranfer Control Protocol)的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
TCP的三次握手
建立起一个TCP连接需要经过“三次握手”:第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求。
1.3.2 UDP
UDP是(User Datagram Protocol)的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket
1.3.3 TCP和UDP的区别
UDP:
1、每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
2、UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
3、UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:
1、面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
2、TCP传输数据没有大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
3、TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
应用:
1、TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
2、UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
2 socket网络编程
2.1什么是socket?
Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
2.2 Socket的原理
Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2、客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
3 基于java的socket网络编程实现
Server端Listen监听某个端口是否有连接请求,Client端向Server 端发出连接请求,Server端向Client端发回Accept接受消息。这样一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
1、创建Socket;
2、 打开连接到Socket的输入/出流;
3、按照一定的协议对Socket进行读/写操作;
4、关闭Socket。
3.1 基于TCP的socket实现
SocketClient.java
public class SocketClient {
public static void main(String[] args) throws InterruptedException {
try {
// 和服务器创建连接
Socket socket = new Socket("localhost",8088);
// 要发送给服务器的信息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("客户端发送信息");
pw.flush();
socket.shutdownOutput();
// 从服务器接收的信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info = br.readLine())!=null){
System.out.println("我是客户端,服务器返回信息:"+info);
}
br.close();
is.close();
os.close();
pw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
SocketServer.java
public class SocketServer {
public static void main(String[] args) {
try {
// 创建服务端socket
ServerSocket serverSocket = new ServerSocket(8088);
// 创建客户端socket
Socket socket = new Socket();
//循环监听等待客户端的连接
while(true){
// 监听客户端
socket = serverSocket.accept();
ServerThread thread = new ServerThread(socket);
thread.start();
InetAddress address=socket.getInetAddress();
System.out.println("当前客户端的IP:"+address.getHostAddress());
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
ServerThread.java
public class ServerThread extends Thread{
private Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
try {
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
while((info=br.readLine())!=null){
System.out.println("我是服务器,客户端说:"+info);
}
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务器欢迎你");
pw.flush();
} catch (Exception e) {
// TODO: handle exception
} finally{
//关闭资源
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在运行时,若先执行SocketClient会提示无法连接到服务器,因为此时没有服务在监听8088端口。此demo是多线程实现,在先启动SocketServer后,服务器会一直监听8088端口,再执行SocketClient就会正常输出结果。
3.2 基于UDP的socket实现
SocketClient.java
public class SocketClient {
public static void main(String[] args) {
try {
// 要发送的消息
String sendMsg = "客户端发送的消息";
// 获取服务器的地址
InetAddress addr = InetAddress.getByName("localhost");
// 创建packet包对象,封装要发送的包数据和服务器地址和端口号
DatagramPacket packet = new DatagramPacket(sendMsg.getBytes(),
sendMsg.getBytes().length, addr, 8088);
// 创建Socket对象
DatagramSocket socket = new DatagramSocket();
// 发送消息到服务器
socket.send(packet);
// 关闭socket
socket.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
SocketServer.java
public class SocketServer {
public static void main(String[] args) {
try {
// 要接收的报文
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 创建socket并指定端口
DatagramSocket socket = new DatagramSocket(8088);
// 接收socket客户端发送的数据。如果未收到会一致阻塞
socket.receive(packet);
String receiveMsg = new String(packet.getData(),0,packet.getLength());
System.out.println(packet.getLength());
System.out.println(receiveMsg);
// 关闭socket
socket.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
运行时,先启动SocketServer,再启动SocketClient,会正常打印数据。在先启动SocketServer时,代码执行到socket.receive(packet)时会一致阻塞在这里,直到启动SocketClient后,SocketServer会继续执行,并将收到SocketClient的信息打印出来。如果是先启动SocketClient,会立即执行完毕,再执行SocketServer时,依旧会阻塞在receive方法处,直到下一次SocketClient的执行。
原文链接:https://blog.csdn.net/u014209205/article/details/80461122
JDBC编程
JDBC 编程步骤
加载驱动程序:
Class.forName(driverClass)
//加载MySql驱动
Class.forName("com.mysql.jdbc.Driver")
//加载Oracle驱动
Class.forName("oracle.jdbc.driver.OracleDriver")
获得数据库连接:
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/imooc", "root", "root");
创建Statement\PreparedStatement对象:
conn.createStatement();
conn.prepareStatement(sql);
完整实例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DbUtil {
public static final String URL = "jdbc:mysql://localhost:3306/imooc";
public static final String USER = "liulx";
public static final String PASSWORD = "123456";
public static void main(String[] args) throws Exception {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
//3.操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
//如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getString("user_name")+" 年龄:"+rs.getInt("age"));
}
}
}
PrepareStatement和Statement的区别
PrepareStatement继承于Statement,包含execute()、 executeQuery() 和 executeUpdate()三种方法
1.PrepareStatement实例包含已经编译的SQL语句,会将SQL语句进行预编译,所以执行速度
比Statement高。
prepareStatment事先对语句进行预处理,效率会高很多。Statment没有进行预处理,所以每次都要载入语句,所以效率比较低
2.PrepareStatement对象中的SQL语句可以包含一个或者多个参数,在SQL语句创建时
可以不指定参数,可以为参数位置保留一个?作为占位符,执行前通过适当的setInt()或者setString()方法来提供
3.用PrepareStatement可以防止sql注入,而Statement不行
所以我们尽量使用PrepareStatement。
————————————————
版权声明:本文为CSDN博主「dulalarepost」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dulalarepost/article/details/100065799
十四.正则表达式
含义:
正则表达式,又称正规表达式、常规表达式,使用字符串来描述、匹配一系列符合某个规则的字符串。
正则表达式组成
普通字符:
大小写字母、数字、标点符号及一些其他符号
元字符:
在正则表达式中具有特殊意义的专用字符
元字符
\:转义字符,!、\n等
^:匹配字符串开始的位置
例: ^a、^the、^#
$:匹配字符串结束的位置
例: word$
.:匹配除\n之外的任意的一个字符
例: go.d、g..d
*:匹配前面子表达式0次或者多次
例:goo*d、go.*d
[list]:匹配list列表中的一个字符
例: go[ola]d,[abc]、[a-z]、[a-z0-9]
[^list]:匹配任意不在list列表中的一个字符
例: [^a-z]、[^0-9]、[^A-Z0-9]
{n,m}:匹配前面的子表达式n到m次,有{n}、{n,}、{n,m}三种格式
例:go{2}d、go{2,3}d、go{2,}d
扩展元字符
+:匹配前面子表达式1次以上
例: go+d,将匹配至少一个o
?:匹配前面子表达式0次或者1次
例: go?d,将匹配gd或god
():将括号中的字符串作为一个整体
例:(xyz)+,将匹配 xyz 整体1次以上,如xyzxyz
|:以或的方式匹配字条串
例1: good|food,将匹配good或者food
例2: g(oo|la)d,将匹配good或者glad
使用grep匹配正则
Grep 【选项】 查找条件 目标文件
-w:表示精确匹配
-E :开启扩展(Extend)的正则表达式
-c : 计算找到’搜寻字符串’的次数
-i :忽略大小写的不同,所以大小写视为相同
-o :只显示被模式匹配到的宁符串
-v:反向选择,亦即显示出没有’搜寻字符串′内容的那一行! (反向查找,输出与查找条件不相符的行)–color=auto : 可以将找到的关键词部分加上颜色的显示喔!
-n :顺便输出行号
基础正则表达式常见元字符
:转义符,将特殊字符进行转义,忽略其特殊意义a.b匹配a.b,但不能匹配ajb,.被转义为特殊意义\\
^:匹配行首,^则是匹配字符串的开始^tux匹配以tux开头的行^^^^
$:匹配行尾,$则是匹配字符串的结尾tux$匹配以tux结尾的行
.:匹配除了换行符 \r\n之外的任意单个字符
[list]:匹配list列表中的一个字符
例: go[ola]d,[abc]、[a-z]、[a-z0-9]
[^list]:匹配任意不在list列表中的一个字符
例: [^a-z]、[^0-9]、[^A-Z0-9]
{n,m}:匹配前面的子表达式n到m次,有{n}、{n,}、{n,m}三种格式
例:go{2}d、go{2,3}d、go{2,}d
原文链接:https://blog.csdn.net/a_b_e_l_/article/details/126239303
实例
public static void main(String [] agrs) {
String content = "1234发覅肯定是粉底哦是覅偶矩3456dsfijkodsikloftejoif3456d'f'x'j'ko'p"+
"萨蒂空腹喝阿斯偶阿三1234dfjidsfij吊死扶伤4567sdahjuadeisfrheufrhywufj5678";
//目标:匹配所有四个数字
//1.\\d表示一个任意的数字
String regStr = "\\d\\d\\d\\d";
//创建模式对象(正则表达式对象):
Pattern pattern = Pattern.compile(regStr);
//创建匹配器:
Matcher matcher = pattern.matcher(content);
//开始匹配
/**
* matcher.find() 完成的任务
* 1.根据指定的规则,定位满足规则的字串
*/
while(matcher.find()){
System.out.println("找到:"+matcher.group(0));
}
}
split用法
@Test
public void splitDemo2(){
String str= "a33b444c555d";
//正则表达式中\d+表示一个或多个数字,java中\\表示一个普通\
//String[] split = str.split(Pattern.compile("\\d+").toString());
//两种写法都是一样的,下面写法简洁
String[] split = str.split("\\d+");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
}
————————————————
版权声明:本文为CSDN博主「一只光头猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41740883/article/details/111696866
十七.类和变量
一、类:
类似于一个模板,它描述一类事物的行为和状态。比如:人、动物。
class关键字用来定义类,多个字母组成的话注意驼峰表示,首字母大写。
二、对象:
类的一个具体实例,有属性和行为。比如:一条狗是一个对象。它的属性有颜色,名字,品种等。它的行为有吃饭,叫,摇尾巴。
三、变量:
分为:成员变量、静态变量、局部变量等
成员变量:定义在类中,方法体之外。非static变量。
静态变量:也叫类变量,也是定义在类中,方法体之外,但是必须声明为Static类型。
局部变量:定位在方法体,语句块或构造方法中的变量。名字首字母小写,多个字母驼峰形式。
四、常量
final关键字用于定义常量,通常都是全部大写。
五、方法
方法定义:修饰符+返回类型+方法名+参数+方法体
当方法不需要返回值的时候,方法的类型必须是void
六、构造方法
每个类都有构造方法,如果没有显示的定义构造方法,Java编译器将会为该类提供一个默认的构造方法。
构造方法的名称必须与类名相同,一个类可以有多个构造方法。不能被static修饰。
目的是为了创建对象
七、总结
·
————————————————
版权声明:本文为CSDN博主「angboom」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/angboom/article/details/118423158
八、补充
类方法就是static修饰的静态方法。(主函数main就是类方法)
当方法是静态方式时,是不能用this来调用本类的方法的(this是指当前对象,类方法中还不存在对象的说法)。静态方法是跟着类走的,所以this表示的当前对象不确定,不能用this调用。直接调用即可
this指当前对象只能在实例方法和构造函数中调用。
在类方法中调用本类的类方法可直接调用。
实例方法也叫做对象方法。
类方法是属于整个类的,
而实例方法是属于类的某个对象的。
由于类方法是属于整个类的,并不属于类的哪个对象,所以类方法的方法体中不能有与类的对象有关的内容。即类方法体有如下限制:
(1) 类方法中不能引用对象变量;
(2) 类方法中不能调用类的对象方法;
(3) 在类方法中不能使用super、this关键字。
(4)类方法不能被覆盖。
如果违反这些限制,就会导致程序编译错误。
与类方法相比,对象方法几乎没有什么限制:
(1) 对象方法中可以引用对象变量,也可以引用类变量;
(2) 对象方法中可以调用类方法;
(3) 对象方法中可以使用super、this关键字。
static块、方法中不可出现this、super关键字。
类方法中可以调用其他类的类方法。
在类方法里,创建一个对象,通过对象调用方法。相当于间接调用
同类中this可以表示隐匿参数类型(比如,在构造器中进行实例域初始化时,如this.data = data;),也可以调用构造器(比如在方法中调用本类的构造器)。其实际就是当前类的类换一个马甲,不至于引发混乱。
比如Sting类, 构造器public string();实际上也可理解为pulic (String) String();或者public this String();。
————————————————
版权声明:本文为CSDN博主「喽耶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45122172/article/details/120691651
在Java中,没有类就不能做任何事情;但是需要注意的是,虽然Java是一门完全面向对象的编程语言,但不代表Java中所有的类都是面向对象,比如说Math类,只需要调用该类的方法去实现某个特定的功能即可,而无需了解具体的实现过程,这也是类的封装所在。
在工程中,我们大多数的类还是面向对象的,包括JDK中的,也包括第三方的,当然也包括我们自定义的类,要使用这些类,那么我们就需要通过这个类来构建一个对象,然后对对象应用方法,在Java中,使用构造器来构建对象,构造器是一个特殊的方法,用来构造并初始化一个对象,构造器的名字必须与类名相同,比如Date类吧,那么Date的构造器就叫做Date,构建一个Date时,加上new关键字即可,像这样:
new Date();
还可以将构建出来的对象放在一个变量中,以便可以多次使用,那么这个变量就是对象变量:
Date date = new Date();
- date就是一个对象变量;
九、对象和对象变量之间的区别:
更好的说明这两者之间的区别,还是通过一个例子来看:
如下:定义一个对象变量deadLine:
Date deadLine ; // 此时deadLine没有引用任何对象
- 这样deadLine就可以引用一个Date类型的对象,但是一定要区分,deadLine并不是一个对象,而是一个对象变量,此时deadLine并没有引用任何Date对象,因此也不能调用任何Date类的方法,否则报错空指针;
- 此时有两种方法对deadLine进行初始化:
// 1. 让deadLine引用一个新创建的Date对象
deadLine = new Date();
// 2. 让deadLine指向一个已经存在的对象变量,比如之前的date对象变量
deadLine = date;
一定需要注意的就是,一个对象变量没有实际包含一个对象,而仅仅是引用了一个对象。
在Java中,任何对象变量的值都是对存储在另一个地方的一个对象的引用,new操作符的返回值就是一个对象,其实对象变量是存储在栈内存中,而对象的引用存储在堆内存中;如下:
Date date = new Date();

这句话包含了两个部分,表达式new Date() 构建了一个Date类型的对象,并把它的值赋于对象变量date,对象变量date中存储了这个对象的引用。
同样,我们将date这个对象变量设置为null,表示这个对象变量不引用任何对象:
date = null;
- 另外需要注意的是,Java中局部变量不会自动的初始化,必须通过new或者显式的置为null进行初始化。
- 感谢你赐予我前进的力量






