sublime小技巧
ctrl+shift+d:复制上一行
ctrl+shift+k:删除整行
(去查sublime快捷键)
直接输入main就可以显示
public static void main(String[] args) {
}
cmd小技巧
按tab键自动补全
在dos里面先javac再java,且java命令的时候不需要后缀名
回车和换行符
回车\r,换行\n
回车是将光标移动到这一行的开头,换行是将光标移动到下一行的开头
javadoc
javadoc配合文档注释可以生成文档
javadoc -d d:\\cls1277\\Java\\javadoc -author -version Hello.java
其他标签自行查看:javadoc标签文档
dos指令
dir:查看当前目录的文件内容
cd:cd /D c: 切换磁盘,直接写cd c:不会切换,需要/D开关
cd :切换到根目录
tree:查看树形目录
exit:退出dos
echo:echo hello > hello.txt 输入内容到文件
type:nul > hello.txt 创建文件
help:help cd(指令)就可以看到参数
+号的使用
都是数值,加法;有一个为字符串,拼接。
查JavaAPI的网站
https://www.matools.com/
(不止java,不止api)
编码问题
ASCII码:1个字节
Unicode码:都是2个字节
Unicode码兼容ASCII码
UTF-8:对Unicode的改进,变长编码,使用1-6个字节表示,字母占1个字节,汉字占3个字节
基本类型转换
byte、short和char之间不会相互自动转换
为什么byte=10不报错?具体数值赋值的时候,先判断是否在byte的范围内,若在范围内,则不报错。
int a=1; byte b=a; //报错
byte、short、char计算时,都转化为int类型运算,结果也就是int,不能往小的数据类型中存储
自动提升原则:表达式结果的类型自动提升为 操作数中最大的类型
基本数据类型转字符串
在变量后加+“”即可。
int n = 100;
String s = n+"";
System.out.println(s);
字符串转基本数据类型
包装类(每个基本数据类型都对应一个包装类)调用parseXX
String s = "100";
int num = Integer.parseInt(s);
System.out.println(num);
运算符
&&:短路与;&:逻辑与,(其他类似)
复合赋值运算符会进行类型转换
byte b = 2;
b = b + 3;
这样会报错,因为b+3会被推导为int类型,不能放到byte类型的b变量中,但是b+=3;不会报错,b=b+1;与b++同理。
关键字和保留字区别
前者是已经有的,如public,static等;后者是保留起来,以后会使用,如goto,byValue,var等。
标识符命名规范
包名:所有字母都小写
类名、接口:所有单词首字母大写(大驼峰)
变量名、方法名:第一个单词首字母小写,第二个单词开始首字母大写(小驼峰、驼峰命名法)
常量名:所有字母均大写,单词间用下划线连接
键盘输入
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
Scanner myScanner = new Scanner(System.in);
String myText = myScanner.next();
int myInt = myScanner.nextInt();
double myDouble = myScanner.nextDouble();
System.out.println(myText);
}
}
原码、反码、补码
break和标签
public static void main(String[] args) {
label1:
for(int i=0; i<3; i++) {
label2:
for(int j=0; j<5; j++) {
if(j==2) {
break label1;
}
System.out.println("j="+j);
}
}
}
输出结果为j=0 j=1 因为break label1,相当于终止了外层i的循环。continue同理。
数组的使用
public static void main(String[] args) {
//first
int a[] = new int [5];
int[] b = new int [5];
//second
int c[]; c = new int [10];
int []d; d = new int [10];
//third
int e[] = {1, 2, 3};
int[] f = {4, 5, 6};
}
数组的赋值机制
引用赋值方式
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 4;
for(int i=0; i<3; i++) {
System.out.println(a[i]);
}
//输出 4 2 3
}
值传递和引用传递
二维数组
java允许数组每一行的列数不同,见下面的third代码
public static void main(String[] args) {
//first
int a[][] = new int[2][3];
int[][] b = new int[2][3];
//second
int c[][]; c = new int[2][3];
int[][] d; d = new int[2][3];
//third
int[][] e = new int[3][];
for(int i=0; i<e.length; i++) {
e[i] = new int[i+1];
for(int j=0; j<e[i].length; j++) {
e[i][j] = i+1;
}
}
for(int i=0; i<e.length; i++) {
for(int j=0; j<e[i].length; j++) {
System.out.print(e[i][j]+" ");
}
System.out.println();
}
//forth
int[][] f = {{1, 2}, {3, 4}, {2}};
}
二维数组还可以” int [] y[] “的形式定义。
对象内存布局
类与对象内存分配机制
与数组相似,因为同为引用数据类型。
java内存分析
栈:一般存放基本数据类型(局部变量)
堆:存放对象(Cat cat,数组等)
方法区:常量池(常量,比如字符出),类加载信息
(先加载类信息,且该类只会加载一次)
方法调用机制
传参机制
基本数据类型:赋值;引用数据类型:赋地址。
引用数据类型为null相当于断开引用连接
public class Object {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name="cls"; cat.age=10; cat.color="white";
Test test = new Test();
test.func(cat);
System.out.println(cat.name+" "+cat.age+" "+cat.color);
//输出 cls 100 white
}
}
class Cat {
String name;
int age;
String color;
}
class Test {
public void func(Cat cat) {
cat.age = 100;
cat = null;
cat.name = "test";
}
}
判断对象是否为同一个
通过输出对象的hashCode,可以简单的当作对象地址,但其实不是对象的实际地址
public class Javap {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.hashCode());
}
}
class Test {
}
或者直接对引用数据类型a和b,判断(a==b)
可变参数
public class Object {
public static void main(String[] args) {
Number number = new Number();
int a = number.add(1, 3, 5, 6);
System.out.println(a);
}
}
class Number {
public int add(int ... nums) {
int res = 0;
for(int num:nums) {
res += num;
}
return res;
}
}
可变参数的实参可以为数组,可变参数的本质就是数组。
可变参数可以与普通参数一起放在形参列表,但是可变参数一定放到最后!不能有两个可变参数!
作用域
局部变量无默认赋值,全局变量有默认赋值。
属性/全局变量可以加修饰符,局部变量不能加修饰符。
构造器
其实就是构造函数,public也可不写。
public class Object {
public static void main(String[] args) {
Number number = new Number(1, 3);
System.out.println(number.num1+" "+number.num2);
}
}
class Number {
int num1, num2;
public Number(int pnum1, int pnum2) {
num1 = pnum1;
num2 = pnum2;
}
}
查看默认构造器
javap反编译指令!(也可直接写javap Test)
参数:-c:返回汇编代码;-v:输出附加信息。
自定义构造器后,默认构造器无效,此时无法使用new Test();构造器,除非显式定义无参构造器。
对象创建流程分析
this
可以使用hashCode判断this与当前对象的相等的本质。
this访问构造器语法,只能放到构造器内,必须放在第一条语句!:
public class Javap {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.a+" "+test.b);
}
}
class Test {
int a, b;
public Test() {
this(1, 3);
}
public Test(int a, int b) {
this.a = a;
this.b = b;
}
}
IDEA
out存放编译后的文件.class,src存放源文件.java
快捷键:alt+/,自动补全。alt+enter,自动导入。ctrl+alt+L,自动排版(可能与QQ的锁定热键冲突)。
运行的快捷键,我改成了ctrl+alt+N,可以在设置中自定义。
自动添加构造器,alt+insert。
查看类的继承关系:ctrl+h,将光标移动到类名上。
定位到哪个类的方法:ctrl+b,将光标移动到方法上。
自动分配变量名:在声明的后面加.var,再按回车,如:
new Scanner(System.in).var
还有很多的快捷键!
模板:main,sout,fori
如果一个项目包含很多个.java文件,不能直接快捷键运行,应该先手动run一次
查看jdk源码:ctrl+b,ctrl+鼠标左键,鼠标右键+goto
1000.for:快捷键for(int i=1; i<=1000; i++)
values.for:foreach循环values
ctrl+J:可以提示所有的快捷键
包的命名
只能包含数字、字母、下划线、小圆点,目录名不能以数字开头,不能是关键字或保留字
一般是小写字母+小圆点,一般是:com.公司名.项目名.业务模块名
常用的包:java.lang.*:基本包,默认引入;java.util.*:工具包;java.net.*:网络包,网络开发;java.awt.*:java界面开发,GUI
最好是需要什么类就导入什么类,不建议使用*导入
package的作用是声明当前类所在的包,放在(类和import的)最上面且只有一句
访问修饰符
public:对外公开
protected:对子类和同一个包中的类公开
默认:向同一个包的类公开
private:只有类本身可以访问,不对外公开
封装
步骤:属性私有化、提供set和get(快捷键:alt+insert->getter and setter)
继承
package com.generate;
public class Pupil extends Student {
public void testing() {
System.out.println(name + "testing");
}
}
package com.generate;
public class Student {
public String name;
public int age;
private double score;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setScore(double score) {
this.score = score;
}
}
父类私有属性和方法不能被子类直接访问。属性可以自己写get函数,方法可以写call函数,都是public的。
子类构造器中默认有个super(),默认调用。子类的构造器(包括有参无参)会调用父类的无参构造器,若父类没有无参构造器,使用super去指定选哪个父类构造器,否则编译错误。
super在使用时,放在子类构造器的第一行。
super和this不能共存在一个构造器内,因为都要放在第一行。
Object是所有类的基类。
子类只能继承一个父类,单继承机制。
本质(注意:父类的name也分配内存):
查找关系:(可访问的,访问修饰符问题)就近原则,如果不能访问(private),但是找到了,会报错,不再继续向上找。
super
super.属性名:访问父类的属性(除private)
super.方法名(参数):访问父类的方法(除private)
**怎么访问父类的父类?**不能super.super,他是查找关系,如果父类有的话就不需要找父类的父类了。
和this的比较
方法重写
子类中方法的参数和方法名一样,子类返回类型和父类一样,或者为父类返回类型的子类。
子类方法不能缩小父类方法的访问权限。
多态
只要能体现多种状态
方法重载体现多态,方法重写体现多态
核心:对象多态
当运行的时候,看运行类型,不是编译类型。
向上转型:父类的引用指向子类的对象,可以调用父类的所有成员(查找),不能调用子类的特有成员(遵守访问权限,因为在编译阶段,能调用哪些是由编译类型决定的)。Animal animal = new Cat();
向下转型:(解决:父类不能调用子类的特有成员问题)Cat cat = (Cat) animal;要求animal之前就指向Cat类型对象。
属性没有重写一说,只看编译类型!
B instanceOf A比较操作符,判断对象B的运行类型是否A类型或A的子类型
动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
多态应用
多态数组、多态参数
Object类
所有对象都可以用它的方法
equals:和==的区别:==既可以判断基本类型,又可以判断引用类型(前者为判断值相等,后者为判断地址相等);equals:只能判断引用类型,子类往往重写,如String判断内容是否相等。
toString
返回:全类名(包名+类名)+‘@’+把hashCode转为16进制
一般重写,输出对象的属性,使用alt+insert中就有
当直接输出一个对象时,toString方法会被默认调用,System.out.println(Person);等价于输出Person.toString()
finalize
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
alt+insert中也有重写的快捷键,可以理解为C++的析构函数
好像是不推荐使用了,已经被弃用了
类变量和类方法
类变量也叫静态变量,static,在类加载的时候就生成了。方法区有个静态域中(jdk8以前),堆中类对应的class对象(jdk8及以后)
可以使用类名.类变量来访问。
类方法也叫静态方法。
静态方法中只能访问静态属性或静态方法,不能用this(super同理),因为有this就有对应的对象了。
普通方法既可以访问静态成员,又可以访问普通成员。
应用:当方法中不涉及到对象相关成员的时候,使用静态方法可以提高开发效率。(希望不创建实例就可以调用,当作工具来使用,如sqrt)
main方法
jvm在调用,main中不可以访问非静态成员(因为它是static修饰的方法)
若要访问非静态成员,要new一个main所在类的对象,再访问
代码块
属于类的成员,类似于方法,但是没有方法名,没有返回值,没有参数,只有方法体,不能显式调用,在加载类的时候隐式调用
修饰符要写只能写static,或者直接不写。
package com.codeblock;
public class Codeblock {
public static void main(String[] args) {
T t = new T(1, 2);
}
}
class T {
int A, B, C;
{
System.out.println("Code Block is used.");
}
public T(int a) {
A = a;
System.out.println("A");
}
public T(int a, int b) {
A = a;
B = b;
System.out.println("A B");
}
public T(int a, int b, int c) {
A = a;
B = b;
C = c;
System.out.println("A B C");
}
}
输出结果
Code Block is used.
A B
相当于对构造器的一种补充机制,可以做初始化的操作。
注意事项:
如果加了static,是静态代码块,则只会在类加载的时候执行一次;反之,为普通代码块,当且仅当创建一次对象执行一次。
如果只是使用静态成员,普通代码块不会执行。
构造器中先调用super(),再调用普通代码块(因为静态代码块在类加载的时候已经被执行了),再执行构造器中其他代码
创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行
④父类的构造方法
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行
⑥子类的构造方法面试题
类什么时候被加载
创建对象实例时,创建子类对象实例时父类被加载,使用类的静态成员时
(使用子类的静态成员的时候,父类也会被加载)
单例模式
单个的实例:某个类只有一个对象实例,包括饿汉式和懒汉式
饿汉式:构造器私有化(防止用户直接new);类的内部创建对象;向外暴露一个静态的公共方法getInstance
package com.single;
public class ehan {
public static void main(String[] args) {
GF gf = GF.getInstance();
}
}
class GF {
private String name;
//为什么是static?因为下面getInstance必须是static,且需要调用这个属性,所以这里必须有static
private static GF gf = new GF("a");
private GF(String name) {
this.name = name;
}
public static GF getInstance() {
return gf;
}
}
为什么叫饿汉式?“很着急,类加载了就创建了这个对象”
懒汉式:使用的时候再创建对象。
步骤:构造器私有化;定义一个static静态属性对象;提供一个public的static方法,可以返回该类型的方法
为什么叫懒汉式?“很懒,不去用它就不去创建”
package com.single;
public class ehan {
public static void main(String[] args) {
BF bf = BF.getInstance();
}
}
class BF {
private String name;
private static BF bf;
private BF(String name) {
this.name = name;
}
public static BF getInstance() {
if(bf==null) {
bf = new BF("a");
}
return bf;
}
}
final
可以修饰类、属性、方法和局部变量(感觉某些情况有点像const)
在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰.
2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰.
3)当不希望类的的某个属性的值被修改,可以用final修饰.
4)当不希望某个局部变量被修改,可以使用final修饰.
final修饰的属性一般叫常量
如果final修饰的属性是静态的,则初始化的位置只能是:
定义时;在静态代码块;不能在构造器赋值
如果类不是final类,但是有final方法,则虽然该方法不能重写,但是可以被继承。
final不能修饰构造方法(即构造器)
final往往和static往往搭配使用效率更高,不会导致类加载。(把一个值放到类当中,但是不希望加载这个类就可以使用这个值)
抽象类
父类方法的不确定性,需要声明但不明确如何实现
当一个类存在抽象方法时,需要将该类声明为抽象类。
abstract class Test {
int a;
public abstract void input();
}
不能被实例化。不一定包含抽象方法。abstract只能修饰类和方法。
如果一个类继承了抽象类,必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类(实现可以只打花括号即可)
抽象方法不能使用private,final,static来修饰,这些关键字与重写相违背。(static:因为其被类名直接调用,但是抽象类无方法体)
实践:模板设计模式
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
类实现接口的时候,必须实现接口的抽象方法。
jdk7之前接口中所有方法都没有方法体。jdk8之后可以有(静态方法default、默认方法static),接口中可以有方法的具体实现。
在接口中,抽象方法可以省略abstract
package com.interface_;
public interface usb {
public void start();
public void end();
}
package com.interface_;
public class phone implements usb{
@Override
public void start() {
System.out.println("start");
}
@Override
public void end() {
System.out.println("end");
}
}
package com.interface_;
public class computer {
public void work(usb _u) {
_u.start();
_u.end();
}
}
package com.interface_;
public class Test {
public static void main(String[] args) {
phone p = new phone();
computer c = new computer();
c.work(p);
}
}
接口本身是抽象的概念,不能被实例化。
接口中所有方法为public方法,不写public但也不是默认的,abstract也可以省略。
抽象类实现接口,可以不用实现接口的方法。
一个类同时可以实现多个接口。
接口名.属性名可以访问接口中的属性。
接口不能继承其他类,但是接口可以继承多个别的接口。
接口和继承
类比为:师徒和父子关系。
子类自动拥有父类的功能,如果子类需要扩展功能,可以用过实现接口的方式扩展。
实现接口是对java单继承机制的补充。
接口在一定程度上实现代码解耦
接口多态
接口类型的变量可以指向实现接口的对象(动态绑定):IA ia = new Test(); class Test implements IA(跟继承向上转型体现多态相似)
多态数组:类似于继承中的向上转型,如果需要调用特有的方法,需要instanceOf向下转型
多态传递:
package com.interface_;
public class Test {
public static void main(String[] args) {
IA ia = new TT();
IB ib = new TT();
}
}
interface IA {}
interface IB extends IA{}
class TT implements IB{
}
内部类
一个类的内部又完整的嵌套了另一个类结构,被嵌套的类叫内部类。
最大的特点:可以直接访问私有属性,体现类与类之间的包含关系。
分类:定义在外部类的方法体内:局部内部类,匿名内部类(*);成员位置上:成员内部类(没static修饰),静态内部类(用static修饰)。
局部内部类:
package com.innerclass;
public class InnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m();
}
}
class Outer {
private int n = 10;
public void m() {
class Inner {
private int n = 20;
//局部内部类
//不能添加访问修饰符,因为是局部变量;但是可以用final修饰,因为局部变量可以加final,表示不可以被继承的
public void f() {
System.out.println("n="+n); //可以直接访问外部类的private成员,就近原则
//外部类在方法中创建Inner的对象,再调用类中的成员
System.out.println("n="+Outer.this.n);
}
}
Inner inner = new Inner();
inner.f();
}
}
匿名内部类:
该类没有名字。匿名内部类是一个对象。
基于接口的匿名内部类:
package com.niming;
public class InnerClass {
public static void main(String[] args) {
outerClass o = new outerClass();
o.test();
}
}
class outerClass {
//基于接口的匿名内部类
private int n = 10;
public void test() {
IA t = new IA() {
@Override
public void ok() {
System.out.println("test!");
}
};
//t的运行类型就是匿名内部类
t.ok();
System.out.println(t.getClass());
//输出:outerClass$1,系统分配给匿名内部类的名字
//匿名内部类使用一次,就不能再使用了,不是t对象!
}
}
interface IA {
public void ok();
}
基于类的匿名内部类:
package com.interfaceInnerClass;
public class InnerClass {
//基于类的匿名内部类
public static void main(String[] args) {
outerClass o = new outerClass();
o.test();
}
}
class outerClass {
private int o = 10;
public void test() {
//关于下面带不带花括号的讨论:
//不带:A的运行类型是A;带:A的运行类型是outerClass$1
//class outerClass$1 extends A {},继承
A a = new A() {//参数会传递给A类的构造器,一般不重写A类的构造器
//匿名内部类可以重写A类的方法
@Override
public void f() {
System.out.println("o="+outerClass.this.o);
//这里可以直接访问到o,即匿名内部类可以直接访问外部的成员包括private
}
};
a.f();
//也可以不用a对象接受,直接new了之后调用.f()
}
}
class A {
private int a = 10;
public void f() {
System.out.println("a="+a);
}
}
若内部类和外部类重名,遵循就近原则,或者使用外部类.this.成员访问。
应用:当作实参直接传递,简洁高效
package com.innerclassusage;
public class InnerClass {
public static void main(String[] args) {
f(new IA(){
@Override
public void show() {
System.out.println("show");
}
});
}
public static void f(IA a) {
a.show();
}
}
interface IA {
public void show();
}
成员内部类:
本质就是成员
package com.sonclass;
public class InnerClass {
public static void main(String[] args) {
T t = new T();
T.inner.test();
}
}
class T {
private int n = 10;
public int m = 15;
class inner {
//可以访问外部类的所有成员,包括私有的
//外部类访问要先创建内部类对象再调用内部类方法
public static void test() {
System.out.println("test!");
}
}
}
外部其他类访问内部类的方法:
package com.neibuclass;
public class innerclass {
public static void main(String[] args) {
//第一种
T t = new T();
T.a a = t.new a();
a.f();
//第二种
T.a a2 = t.geta();
a2.f();
//第三种
T.a a3 = new T().geta();
a3.f();
}
}
class T {
class a {
public void f() {
System.out.println("cls tcl");
}
}
public a geta() {
return new a();
}
}
如果重名,与之前的处理方法一样。
静态内部类:
唯一的区别就是有static修饰
package com.staticclass;
public class innerclass {
public static void main(String[] args) {
outerClass.innerClass.print();
outerClass o = new outerClass();
o.f();
}
}
class outerClass {
private int n = 10;
private static String name = "cls tcl";
static class innerClass {
//只可以访问外部类static的成员,不可以访问非静态成员
public static void print() {
System.out.println("name="+name);
}
public void print2() {
System.out.println("name2="+name);
}
}
public void f() {
innerClass.print();
innerClass i = new innerClass();
i.print2();
}
}
外部其他类访问静态内部类
package com.staticclass;
public class elseclass {
public static void main(String[] args) {
//第一种
A.B b = new A.B();
b.print();
//第二种
b = A.getB();
b.print();
//第三种
A a = new A();
b = a.getBB();
b.print();
}
}
class A {
private int n = 10;
static class B {
public int m = 15;
public void print() {
System.out.println("m="+m);
}
}
public static B getB() {
return new B();
}
public B getBB() {
return new B();
}
}
重名的话处理与上文差不多,但是没有this,因为是static
枚举类
有确定个数个值,只读不需要修改,枚举是一组常量的类,有限的特定的对象。
自定义枚举:
构造器私有化,防止new;去掉set方法,防止修改;在类内部直接创建固定的对象(public,static)
(优化:加个final,防止类加载)
enum关键字:
valueOf的实际意义:规范用户输入;compareTo:返回编号的相减的值。
package com.enum_;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
System.out.println(Season.SUMMER);
}
}
enum Season {
//用逗号隔开,且常量对象写到最前面
//使用无参构造器,小括号可以省略!
TEST,
TEST2(),
SPRING("spring","warm"),
SUMMER("summer","hot"),
AUTUMN("autumn","sats"),
WINTER("winter","cold");
private String name;
private String desc;
Season(){}
Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
不能再继承,但是可以实现接口。
注解
三个基本注解:Override,Deprecated,SupressWarnings
Override:限定某个方法是重写父类的方法。编译器检查是否真的重写了父类的方法,如果没有重写,则报错。
关于@interface:不是接口,而是注解类,jdk1.5之后加入的
Deprecated:表示某个程序的元素已经过时了,过时并不代表不能用,即不推荐使用,可以做到版本的兼容和过渡。
SupressWarnings:抑制编译警告,@SupressWarnings({“all”,“unused”})(需要查文档),作用范围与写的位置有关。
元注解
@Target是修饰注解的注解,叫元注解。(看代码的时候知道在干嘛就可以)
Retention:指定注解的作用范围,SOURCE,CLASS,RUNTIME
Target:指定注解可以在哪些地方使用
Documented:指定该注解是否会在javadoc体现
Inherited:子类会继承父类的注解。
异常
try-catch快捷键:ctrl+alt+t,再选择try-catch
即使出现异常,可以继续执行。
异常分为两大类:error(JVM无法解决的严重问题,严重错误,程序会崩溃),exception(由于编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,分为运行时异常和编译时异常)
常见的运行时异常:
NullPointerException:空指针异常,对象没有被创建就被使用
ArithmeticException:数学运算异常
ArrayIndexOutOfBoundsException:数组下标越界异常
ClassCastException:类型转换异常,向上转型和向下转型的时候抛出
NumberFormatException:数字格式不正确异常,这种异常可以确保输入的是数字
常见的编译异常:
SQLException//操作数据库时,查询表可能发生异常
IOException//操作文件时,,发生的异常
FileNotFoundException//当操作一个不存在的文件时,发生异常
ClassNotFoundException1/加载类,而该类不存在时,异常
EOFException//操作文件,到文件末尾,发生异常
illegalArguementException//参数异常
异常体系图
(要会在idea中画这种类图)
异常处理
两种方式:try-catch-finally(程序员在代码中捕获发生的异常,自行处理),throws(异常抛出,交给调用者处理,最顶级的处理者是JVM)
finally:不管try是否有异常,finally始终要执行,通常将释放资源的代码放到这。
throws:被调用的方法发生异常之后不处理异常,直接抛给调用它的方法,再由上一个方法决定怎么处理,如果都不处理,最后给JVM处理。
JVM处理异常:输出异常信息,退出程序。没有显式定义异常捕获,默认就是throw。
try-catch
一旦出现异常,直接进入catch代码块,不继续执行try内后面的内容。
可以有多个catch语句捕获不同异常,要求父类异常在后,子类异常在前,若发生异常,只会匹配一个catch。
可以使用try-finally,由于没有捕获异常,程序会直接崩掉,执行一段代码,不管是否发生异常,都必须执行某个业务逻辑,finally执行完毕后程序退出。
throws
package com.excp;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
try {
f();
} catch (ArithmeticException e) {
System.out.println("cls tcl");
}
}
//throws抛出的可以是这个异常,也可以是这个异常的父类
public static void f() throws ArithmeticException {
int num1 = 10, num2 = 0;
System.out.println(num1/num2);
}
}
编译异常,程序必须处理,throws给调用者的时候,调用者也必须要处理。
子类重写父类方法时,对抛出的异常:要么一致,要么是父类抛出异常的子类。
throws和try-catch二选一。
自定义异常
package com.defineexcp;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
int n = 15;
if(!(n>=1&&n<=10)) {
throw new DefineException("no!");
}
}
}
class DefineException extends RuntimeException {
public DefineException(String message) {
super(message);
}
}
一般情况下,自定义异常继承RuntimeException
throw和throws
throw后跟异常对象,throws后跟异常类型。
包装类
针对八种基本数据类型定义相应的引用类型,包装类(wrapper)中有一些方法。
装箱和拆箱:
装箱:基本类型到包装类;拆箱:反之。
包装类的其他细节代码:
package com.wapper_;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
//装箱和拆箱:
int n = 10;
//自动装箱,底层使用的是:Integer中的valueOf方法
Integer N = n;
//自动拆箱,底层使用的是:Integer中的intValue方法
int n2 = N;
//包装类->String
Integer i = 100;
String s = i+"";
String s2 = i.toString();
String s3 = String.valueOf(i);
//String->包装类
String str = "12345";
Integer i1 = Integer.parseInt(str);
Integer i2 = new Integer(str);
//包装类常用方法
//算了,不写了,自己查文档多写代码吧。
//-128~127之间是同一地址,其他是new的新的Integer
Integer N1 = 1;
Integer N2 = 1;
System.out.println(N1==N2); //true
Integer N3 = 128;
Integer N4 = 128;
System.out.println(N3==N4); //false
}
}
String类
使用unicode编码(一个字符,不论字母还是汉字,都占两个字节)。不能被继承。
底层仍然是个char数组(private final char value[])!用于存放字符串内容,是个常量,一旦赋值不能被修改。
(是说的地址不可以修改,值可以修改,类似于指针常量!)这也是final的作用,不允许指向新的内存空间。
可以串行化,可以比较大小。
字符串特性:
常量相加看池中的内容,变量相加看堆中的内容!
.intern是指向对应池中的地址。
常用方法:
equals //区分大小写,判断内容是否相等
equalslgnoreCase//忽略大小写的判断内容是否相等length/获取字符的个数,字符串的长度
indexOf//获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
lastIndexOf//获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1substring 1/截取指定范围的子串
trim//去前后空格
charAt:获取某索引处的字符,注意不能使用Str[index]这种方式.
toUpperCasetoLowerCaseconcat
replace替换字符串中的字符
split 分割字符串,对于某些分割字符,我们需要转义比如等
compareTo //比较两个字符串的大小toCharArray 1/转换成字符数组
format //格式字符串,%s字符串%c字符%d 整型%.2f 浮点型案例,将一个人的信息格式化输出.
StringBuffer类
它的内部实现时,有属性char[] value,不是final修饰的,因此存放在堆中。
StringBuffer是final类。
和String对比:
String保存字符串常量,里面的值不能更改,每次对String的更新实际上是更改地址,效率低
StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用更新地址,效率较高
和String的转换:
package com.string_;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
//String->StringBuffer
String str = "cls tcl";
//第一种
StringBuffer sb1 = new StringBuffer(str);
//第二种
StringBuffer sb2 = new StringBuffer();
sb2.append(str);
//StringBuffer->String
//第一种
String s1 = sb1.toString();
//第二种
String s2 = new String(sb1);
System.out.println(sb1+" "+sb2+" "+s1+" "+s2);
}
}
常用方法:
1)增append
2)删delete(start,end)
3)改replace(start,end,string) //将start----end间的内容替换掉,不含end
4)查indexOf //查找子串在字符串第1次出现的索引,如果找不到返回-1
5)插insert
6)获取长度length
StringBuilder类
此类提供与StringBuffer兼容的API,但不保证同步(StringBuilder不是线程安全的)
该类被设计用作StringBuffer的一个简易计算,单线程优先采用StringBuilder,大多数实现中它比StringBuffer快。
除了线程方面,其他与StringBuffer相似。
String,StringBuffer,StringBuilder比较
(1)StringBuilder和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
(2)String:不可变字符序列,效率低,但是复用率高。
(3)StringBuffer:可变字符序列、效率较高(增删)、线程安全
(4)StringBuilder:可变字符序列、效率最高、线程不安全
(5)String使用注意说明:
string s=“a”;//创建了一个字符串
S += “b”;//实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个子符串s+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大重副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能=>
(6)结论:如果我们对String 做大量修改不要使用String
一些常用的类
Math,System,BigInteger,BigDecimal,Date,Calender,LocalDate
集合
单列集合:单个对象
ArrayList和LinkedList比较:
如何选择ArrayList和LinkedList:
如果我们改查的操作多,选择ArrayList
如果我们增删的操作多,选择LinkedList
一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList.
package com.set_;
import java.util.*;
/**
* @author cls1277
*/
public class Hello {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
//Collection接口常用的方法:
//add,remove,contains,size,isEmpty,clear,addAll,containsAll,removeAll
list.add(1277); list.add("cls"); list.add(3.14);
//Collection的遍历方式:
//第一种:iterator:最开始不是指向第一个,而是第一个的上面,next是先移动再输出
Iterator it = list.iterator();
//快捷键:itit+回车
while(it.hasNext()) {
System.out.print(it.next()+" ");
}
//退出循环,iterator指向最后一个元素,不可再next
System.out.println();
//第二种:增强for循环:底层是迭代器,是简化版的迭代器
//快捷键:I+回车 / iter+回车
for(Object it1: list) {
System.out.print(it1+" ");
}
System.out.println();
//List接口:
//1.List元素添加顺序与取出顺序相同,且可重复
list.add("cls");
//2.List元素每个元素都有对应的顺序索引,支持索引
System.out.println(list.get(0));
//3.List实现子类很多,常用的是Vector,ArrayList,LinkedList
//List接口的常用方法:
//void add(int index, Object ele)在index位置插入ele元素
//boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
//Object get(int index):获取指定index位置的元素
//int indexOf(Object obj):返回obj在集合中首次出现的位置
//int lastIndexOf(Object obj):返回obj在当前集合中未次出现的位置
//Object remove(int index):移除指定index位置的元素,并返回此元素
//Object set(int index,Object ele):设置指定index位置的元素为ele,相当于是替换
//List subList(int fromIndex, int tolndex):返回从fromlndex到tolndex位置的子集合
//List的遍历方法:
//iterator和增强for
//第三种
for(int i=0; i<list.size(); i++) {
System.out.print(list.get(i)+" ");
}
System.out.println();
//ArrayList:
//空可以放到里面,且多个
//底层是数组实现的
//基本等同于Vector,但是ArrayLiat线程不安全,效率高
list.add(null);
//扩容机制:
//ArrayList中维护了一个Object类型的数组elementData : transient Object[] elementData;
//当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
//如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
//Vector:
//是线程安全的,需要线程同步时,考虑使用Vector
//LinkedList:
//底层实现了双向链表和双向队列的特点
//可以添加任意元素包括null,线程不安全,没有实现同步
//LinkedList底层维护了一个双向链表、
//LinkedList中维护了两个属性first和last分别指向首节点和尾节点
//每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
//所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
System.out.println("list="+list);
//Set接口常用方法:
//添加和取出顺序不同;没有索引;不允许重复元素,最多一个null
//常用方法与Collection一样!
//遍历方法:与Collection一样!
Set set = new HashSet();
set.add("cls"); set.add("tcl");
set.add(1277); set.add(null);
for (Object o : set) {
System.out.print(o+" ");
}
System.out.println();
//HashSet:
//HashSet底层其实是HashMap,数组+单向链表
//添加一个元素时,先得到hash值-会转成->索引值
//找到存储诸数据表table,看这个索引位置是否已经存放的有元素
//如果没有,直接加入
//如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
//在Java8中,如果一条链表的元素个数到 TREEIFY_THRESHOLD(默认是8),并且table的大小>=
//MIN TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
//若不大于等于64,则先把table数组扩容(按两倍扩,直到到64,再树化)
//LinkedHashSet:
//LinkedHashSet是HashSet的子类
//LinkedHashSet底层是一个 LinkedHashMap,底层维护了一个数组+双向链表(不同列之间的链表之间相连)
//LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
//LinkedHashSet不允许添加重复元素
LinkedHashSet set2 = new LinkedHashSet();
set2.add("cls"); set2.add("tcl"); set2.add(null); set2.add(1277);
//输入顺序和输出顺序相同
for (Object o : set2) {
System.out.print(o+" ");
}
}
}
双列集合:键值对形式
package com.map_;
import java.util.*;
/**
* @author cls1277
*/
public class Hello {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//Map特点:
//Map用于保存具有映射关系的数据:Key-Value
//Map中的key 和 value可以是任何引用类型的数据,会封装到HashMap$Node对象(静态内部类)中
//为了方便程序员遍历,创建了entrySet集合,数据类型为Entry,每一个Entry有key和value,其实存放的是HashMap$Node的地址
//因为HashMap$Node实现了Entry<K,V>的接口
//Map 中的key不允许重复,原因和HashSet一样,重复了就要替换掉。
//Map 中的value可以重复
//Map 的key可以为null, value也可以为null,注意key为null, 只能有一个,value为null ,可以多个
//常用String类作为Map的key(一般来说)
//key 和 value 之间存在单向一对一关系,即通过指定的key 总能找到对应的value
//没有实现同步,线程不安全
//Map常用方法:
Map map = new HashMap();
//put,remove,get,size,isEmpty,clear,containsKey,keySet,entrySet,values
map.put("1","cls");
map.put("0","tcl");
map.put("2","1277");
map.put("100","cls1277");
//Map遍历方式:
//第一种
Set keyset = map.keySet();
for (Object o : keyset) {
System.out.println(o+":"+map.get(o));
}
System.out.println();
//第二种
Iterator it = keyset.iterator();
while (it.hasNext()) {
Object next = it.next();
System.out.println(next+":"+map.get(next));
}
//第三、四、五种
Collection values = map.values();
System.out.println();
//三种遍历Collections方式
//增强for,迭代器,下标索引
//第六、七种
Set set = map.entrySet();
//增强for
for (Object o : set) {
Map.Entry m = (Map.Entry)o;
System.out.println(m.getKey()+":"+m.getValue());
}
//或者可以迭代器
//HashMap:
//是Map的实现类,基本与上面代码相同,在HashSet源码中也有
//Hashtable:
//存放的元素是键值对:即 K-V
// hashtable的键和值都不能为null,否则抛出空指针异常
//hashTable使用方法基本上和HashMap一样
//hashTable是线程安全的,hashMap是线程不安全的
//底层:底层是一个数组,初始大小为11,Hashtable$Entry类型,是内部类,它实现Map.Entry
//threshold临界值:8 = 11*loadFactor(加载因子,0.75),与HashMap类似
//扩容:×2+1(11扩容为23)
//Properties:
//Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。
//他的使用特点和Hashtable类似
//Properties还可以用于从 xxx.properties文件中,加载数据到Properties类对象并进行读取和修改
//说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流举例,有兴趣可先看文章
//https://www.cnblogs.com/xudong-bupt/p/3758136.html
}
}
如何选择?
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1)先判断存储的类型(一组对象或一组键值对)
2)一组对象: Collection接口
允许重复:List
增删多:LinkedList [底层维护了一个双向链表]
改查多:ArrayList [底层维护Object类型的可变数组]
不允许重复:Set
无序: HashSet[底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)】
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
3)一组键值对:Map
键无序: HashMap [底层是:哈希表jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap
健插入和取出顺序一致:LinkedHashMap
读取文件Properties
Collections工具类
reverse(List):反转 List 中元素的顺序
shuffle(List):对List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
sort(List, Comparator):根据指定的Comparator 产生的顺序对List集合元素进行排序
swap(List, int, int):将指定list集合中的i处元素和j处元素进行交换
package com.collect;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* @author cls1277
*/
public class Hello {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(1); list.add(100); list.add(5); list.add(200);
Collections.sort(list);
System.out.println(list);
ArrayList list2 = new ArrayList();
list2.add(1); list2.add(100); list2.add(5); list2.add(200);
Collections.sort(list2, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int n1 = (Integer)o1, n2 = (Integer)o2;
return n2-n1;
}
});
System.out.println(list2);
}
}
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator):根据Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List 对象的所有旧值
泛型
T和E只能是引用类型,不能是基本数据类型;可以传入规定的类型或者其子类
自定义泛型:
package com.fanxing;
import java.util.ArrayList;
/**
* @author cls1277
*/
public class Hello {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//类型推断
Test<String> s = new Test<>();
s.setA("111");
System.out.println(s.getA());
Test<String> s2 = new Test<String>();
s2.setA("222");
System.out.println(s2.getA());
//默认为Object类型
ArrayList<Object> objects = new ArrayList<>();
Test t = new Test();
//普通成员可以使用泛型(属性、方法)
//使用泛型的数组,不能初始化,不能确定类型,不能实例化,也就不能初始化,即没确定之前不能new
//静态方法中不能使用类的泛型,静态方法可能会提前类加载,但是此时不能确定类型
//泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
//如果在创建对象时,没有指定类型,默认为Object
//泛型的标识符一般为单个大写字母
}
}
//自定义泛型类
class Test<E> {
public E a, b;
public void setA(E a) {
this.a = a;
}
public E getA() {
return a;
}
}
//自定义泛型接口
interface IA<E, T> {
//接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
//泛型接口的类型,在继承接口或者实现接口时确定
//没有指定类型,默认为Object
public void hi (E e);
public E ok(E e, T t);
}
//继承接口确定类型
interface IB extends IA<String, Integer> {}
class CB implements IB {
//自动填充IB确定的类型
@Override
public void hi(String s) {
}
@Override
public String ok(String s, Integer integer) {
return null;
}
}
//实现接口确定类型
class CC implements IA<String, Integer> {
//自动确定实现接口时确定的类型
@Override
public void hi(String s) {
}
@Override
public String ok(String s, Integer integer) {
return null;
}
}
//泛型方法,可以定义在普通类中,也可以定义作泛型类中
//当泛型方法被调用时,类型会确定
//public void eat(E e)0{},修饰符后没有<T,R..> eat方法不是泛型方法,而是使用了泛型
class CD {
public int n = 10;
public<E> E geta(E a) {
return a;
}
}
class CE<E> {
public E e;
//是泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
public <E> E getEE(E e) {
return e;
}
//不是泛型方法,而是方法使用了泛型
public E getE(E e) {
return e;
}
}
泛型的继承和通配符:
package com.fanxingdetail;
import java.util.ArrayList;
import java.util.List;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
//泛型不具备继承性
// List<Object> list = new ArrayList<String>();//对吗?不对!
//<?>:支持任意泛型类型
//<? extends A>:支持A类以及A类的子类,规定了泛型的上限
//<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
ArrayList<CA> cas = new ArrayList<>();
ArrayList<CB> cbs = new ArrayList<>();
ArrayList<CC> ccs = new ArrayList<>();
print1(cas); print1(cbs); print1(ccs);//无报错
print2(cas); print2(cbs); print2(ccs);//第一个报错
print3(cas); print3(cbs); print3(ccs);//第三个报错
}
public static void print1(List<?> l) {
for (Object o : l) {
System.out.println(o);
}
}
public static void print2(List<? extends CB> l) {
for (CB cb : l) {
System.out.println(cb);
}
}
public static void print3(List<? super CB> l) {
for (Object o : l) {
System.out.println(o);
}
}
}
class CA {
}
class CB extends CA {
}
class CC extends CB {
}
Junit单元测试类
测试框架,是java的单元测试框架
package com.junit_;
import org.junit.jupiter.api.Test;
/**
* @author cls1277
*/
public class Junit_ {
public static void main(String[] args) {
}
@Test
public void f1() {
System.out.println(1);
}
@Test
public void f2() {
System.out.println(2);
}
}
线程
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
可能并发和并行同时进行。
程序获取当前电脑的cpu个数:
package com.cpu_;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
int cpuNums = runtime.availableProcessors();
System.out.println(cpuNums);
}
}
线程的使用方法:
package xiancheng;
/**
* @author cls1277
*/
public class XianCheng {
public static void main(String[] args) {
//线程的使用方法:1.继承Thread类;2.实现Runnable接口
//使用jconsole:运行程序后,在终端输入jconsole,可以看到线程的运行情况
//返回主线程的名字:main
System.out.println(Thread.currentThread().getName());
//1.继承Thread类
T1 t1 = new T1();
//启动线程->最终会执行run方法
//Q:这里为什么是start而不是run?
//A:直接调用run,相当于main线程调用的子线程,并没有真的开线程,T1线程类输出的线程名为main
//如果直接调用run,则必须等run结束后再继续main线程的执行
//本质上就相当于run方法了
t1.start();
//当main线程启动一个子线程后Thread-0后,主线程不会阻塞,会继续执行
//main线程挂了之后,不会影响子线程的运行
for(int i=0; i<80; i++) {
System.out.println("i="+i);
//主线程休眠1s,与T1线程交替执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//2.实现Runnable接口
//因为Java是单继承的,所以继承Thread类在一些已经继承过的不能用了,所以只能实现接口
T2 t2 = new T2();
//Runnable接口中没有start方法,创建Thread对象,然后再调用start方法
//为什么可以把t2放给Thread对象呢?使用了设计模式:静态代理模式。
//t2实现了Runnable,Thread的构造器中是Runnable类型的,所以t2可以直接放到Thread的构造器中
Thread thread = new Thread(t2);
thread.start();
}
}
//当一个类继承了Thread类,该类就可以当作一个线程
class T1 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
int cnt = 0;
while(true) {
System.out.println("cls tcl!");
cnt++;
if(cnt==80) {
//当函数退出,该线程也就退出了
// break;
return;
}
//让该线程休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class T2 implements Runnable {
@Override
public void run() {
int cnt = 0;
System.out.println(Thread.currentThread().getName());
while(true) {
System.out.println("cls tcl!");
cnt++;
if(cnt==5) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程终止:(通知的方式)
package com.stoppro_;
/**
* @author cls1277
*/
public class StopPro {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
Thread.sleep(5000);
t.setLoop(false);
}
}
class T extends Thread {
private boolean loop = true;
private int cnt = 0;
//main线程通知T线程终止
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while(loop) {
cnt++;
System.out.println(cnt+"cls tcl!");
if(cnt==100) {
return ;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程常用方法:
setName//设置线程名称,使之与参数name 相同
getName//返回该线程的名称
start//使该线程开始执行;Java虚拟机底层调用该线程的 start0方法
run//调用线程对象 run方法;
setPriority //更改线程的优先级
getPriority//获取线程的优先级
sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt //中断线程,不是结束线程,一般用于中断正在休眠的线程
线程优先级范围:MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5
如果线程被interrupt了,则进入catch的代码块中!
yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功(cpu可能觉得自己可以,就礼让不成功)
join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
用户线程和守护线程:
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。常见的守护线程:垃圾回收机制
package com.promethod;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) throws InterruptedException {
T t = new T();
//注意顺序!!!
t.setDaemon(true);
t.start();
for(int i=0; i<5; i++) {
System.out.println("n tql!");
Thread.sleep(1000);
}
}
}
class T extends Thread {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println("cls tcl!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程的生命周期:(getState获取进程当前状态;Thread.State是个枚举类,包含线程可以存在的状态,用于判断)
线程同步机制:
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
package com.tongbu;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
Sell sell = new Sell();
// new Thread(sell).start();
// new Thread(sell).start();
// new Thread(sell).start();
Thread thread = new Thread(sell);
Thread thread1 = new Thread(sell);
Thread thread2 = new Thread(sell);
thread.setName("1");
thread1.setName("2");
thread2.setName("3");
thread.start();
thread1.start();
thread2.start();
}
}
class Sell implements Runnable {
private int ticketNum = 100;
public synchronized void f() {
if(ticketNum<=0) {
System.out.println("over!");
return ;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" sell!");
ticketNum--;
}
@Override
public void run() {
while (true) {
f();
if(ticketNum<=0) break;
}
}
}
互斥锁
package com.tongbu;
/**
* @author cls1277
*/
public class Hello {
public static void main(String[] args) {
Sell sell = new Sell();
// new Thread(sell).start();
// new Thread(sell).start();
// new Thread(sell).start();
Thread thread = new Thread(sell);
Thread thread1 = new Thread(sell);
Thread thread2 = new Thread(sell);
thread.setName("1");
thread1.setName("2");
thread2.setName("3");
thread.start();
thread1.start();
thread2.start();
}
}
class Sell implements Runnable {
private int ticketNum = 100;
//方法:锁在了this上
public synchronized void f() {
if(ticketNum<=0) {
System.out.println("over!");
return ;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" sell!");
ticketNum--;
}
//代码块:锁在了synchronized中的括号中
public void m() {
synchronized (this) {
if(ticketNum<=0) {
System.out.println("over!");
return ;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+" sell!"+" "+ticketNum);
ticketNum--;
}
}
//锁不在this上,因为没有this,加在了Sell.class上
public synchronized static void n() {
}
//上面等价于
public static void n2() {
synchronized (Sell.class) {
}
}
@Override
public void run() {
while (true) {
// f();
m();
if(ticketNum<=0) break;
}
}
}
注意事项和细节:
同步方法如果没有使用static修饰:默认锁对象为this
如果方法使用static修饰,默认锁对象:当前类.class
实现的落地步骤:
需要先分析上锁的代码选择同步代码块或同步方法
要求多个线程的锁对象为同一个!
IO流
创建文件:
package com.file_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
/**
* @author cls1277
*/
public class file_ {
public static void main(String[] args) {
}
//方式1
@Test
public void c1() {
String path = "D:\\cls1277\\Java\\IDEA\\new1.txt";
File file = new File(path);
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//方式2
@Test
public void c2() {
File file = new File("D:\\cls1277\\Java\\IDEA\\");
File file1 = new File(file, "new2.txt");
try {
file1.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//方式3
@Test
public void c3() {
File file = new File("D:\\cls1277\\Java\\IDEA\\", "new3.txt");
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
获取文件信息:
getName,getAbsolutePath,getParent,length,exists,isFile,isDirectory
目录操作:
delete:删除空目录或文件;mkdir:创建目录,多级目录用mkdirs,无参。
InputStream、OutputStream、Reader、Writer都是抽象类
io流:https://blog.csdn.net/qq_44715943/article/details/116501936