删除指定行:Ctrl+D
复制当前行到下一行:Ctrl+Alt+向下光标
运行代码:Alt+B
查看类的继承关系:把光标放在类名上,Ctrl+H
补全代码:Alt+/
注释:Ctrl+/
>>
(算术右移):低位溢出,符号位不变,并用符号位补溢出的高位(相当于/2)>>>
(无符号右移):低位溢出高位补零//动态初始化//方式一int[] arr1= new int[5];//方式二int[] arr2;arr2 = new int[10];//静态初始化int[] a = {1,2,3,4,5};
char
char a='A';System.out.println((int)a)
转换规则:
char->int->long->float->doublebyte->short->int->long->float->double
细节:
当多种数据类型混合运算时,系统首先将所有数据类型转换为容量最大的数据类型,然后进行计算
int a=1;float f = a + 1.1; //错误,1.1为double类型float f = a + 1.1F; //正确
不可以把容量大的数据类型赋值给容量小的数据类型
将一个具体的数字赋给byte类型,只要数字在-128~127就可以
byte,short和char之间可以进行计算,在计算时首先转换为int类型(赋值操作先对等号右边进行处理)
byte b1=1,b2=2;byte b3=b2 + b1; //这样是错误的,只要有byte类型,计算时就会转换为int在计算
boolean不参与类型转换
byte和short都不能和char进行自动类型转换
c++和java中区别
int a = 20;char b = 'A';int c = b + a; //85char d = b + 20; //U 在c++中这段代码是正确的int a = 20;char b = 'A';int c = b + a; //85char d = b + 20; //U 在java中就会报错,需要进行强制类型转换
当想要把一个容量大的数据类型赋值给容量小的数据类型,就要进行强制类型转换,会有精度损失
细节:
基本数据类型->String
在基本数据类型后加一个字符串就可以实现
int a=10;//方式一String str = a + "";//fstr = String.valueOf(a);
String->基本数据类型
使用包装类提供的方法实现转换
String str = "123";int a = integer.parseInt(str);
String->char
使用String类的charAt()
来获取字符串中的字符
属性,也可以叫做字段,成员变量。属性如果未赋值默认值为:
在每创建一个对象的同时,就会有一个this指针指向当前的对象。所以每创建十个对象就会有十个this指针指向各自的对象
this在类中可以用来访问该类的成员,包括成员变量,成员函数,构造函数。
static可以修饰:变量,代码块,方法(不能修饰构造方法)
静态代码块:static修饰代码块,属于类。类加载一次,静态代码块执行一次。需要注意的是:因为代码加载顺序的问题,**静态代码块,是不能写在静态方法内部的,只能写在类的内部。同理,静态方法内部也是不能定义静态变量的。**也就是说静态内部不能再有静态
成员代码块:属于对象实例。调用一次构造方法创建一个对象,成员代码块执行一次。先执行成员代码块,再执行构造方法
局部代码块:属于方法,执行一次方法,局部代码块执行一次。方法中的代码顺序执行,不会提前执行。
main()
main()
有虚拟机调用[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wZlu1fCP-1632385272609)(D:\document\小林code\小林图片\Java访问权限修饰符.png)]
(1)Java不支持多继承,也就是说子类至多只能有一个父类。
(2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。
(3)子类中定义的成员变量和父类中定义的成员变量相同时,子类中会有两个同名的变量.
(4)子类中定义的成员方法,并且这个方法的名字,返回类型,以及参数个数和类型与父类的某个成员方法完全相同,则父类的成员方法不能被继承,使用自己的方法。
继承中的成员变量:
继承的使用细节:
super关键字的作用:super代表父类的引用,可以访问父类非私有的属性,方法和构造器
super的使用细节:
继承体系中的类加载机制:
继承中属性和方法访问机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xVb02PDe-1632385272611)(D:\document\小林code\小林图片\继承内存关系图.png)]
程序员需要在硬盘的某个位置新建一个.Java扩展名的文件,该文件被称为Java源文件,源文件当中编写的是Java源代码/源程序,而这个源程序是不能随意编写的,必须符合Java语法规则
Java程序员需要使用JDK
当中自带的Javac.exe
命令进行Java程序的编译。一个Java源文件可以编译生成多个.class文件(字节码文件)
运行阶段的过程:
1. 打开DOS命令窗口,输入Java A ,Java.exe
命令会启动Java虚拟机(JVM
),JVM
会启动类加载器classLoader
,classLoader
会去硬盘上搜索A.class
文件.
2. 找到该文件将该字节码文件装载到JVM
中,JVM
将.class字节码文件解释成二进制形式,然后操作系统执行二进制和底层硬件平台进行交互.
属性的官方定义:SUN官方定义为属性是指get或者set方法名 去掉get或者set后,把剩余的部分首字母改为小写后,即为这个类的属性。
大多数情况下,成员变量和属性的概念是一样的。
Java的引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的类型决定。如果编译时类型和运行时类型不一致,会出现所谓的多态。因为子类其实是一种特殊的父类,因此java
允许把一个子类对象直接赋值给一个父类引用变量,无须任何类型转换,或者被称为向上转型,由系统自动完成。
// 编译时类型 运行时类型 Animal cat=new Cat();
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法(意思是说:编写代码时,只能调用父类中具有的方法,如果子类重写了该方法,运行时实际调用的是运行时类型的该方法。程序在编译时,会在编译类型中检查是否具有所调用的方法,如果编写代码时,使用引用变量调用子类中的特有方法,或者调用重载了父类中的方法,而父类中找不到该方法,则会报编译错误),因此,编写Java代码时,引用变量只能调用声明该变量所用类里包含的方法。与方法不同的是,对象的属性则不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是试图访问它编译时类所定义的属性,而不是它运行时所定义的属性。所以,要访问子类中特有的方法和属性,在编写代码时,则必须进行类型转换。
代码演示:
class Father{ public void method(){ System.out.println("父类方法 "+this.getClass()); } public static void main(String[] args) { Father instence =new Son(); instence.method(); }}class Son extends Father{ public void method(){ System.out.println("子类方法 "+this.getClass()); }}结果:子类方法 class reviewInherit.Son
class Father{ public void method(){ System.out.println("父类方法 "+this.getClass()); } public static void main(String[] args) { Father instence =new Son(); instence.method(); }}class Son extends Father{}结果: 父类方法 class reviewInherit.Son
class Father{ private String name="父类"; public static void main(String[] args) { Father instance =new Son(); System.out.println(instance.name); }}class Son extends Father{ private String name="子类";}结果:父类
也可通过以下代码证明:
class Father{ private String name="父类";}class Son extends Father{ private String name="子类"; public static void main(String[] args) { Father instance =new Son(); System.out.println(instance.name); }}instance.name 处直接报错 'name' has private access in 'reviewInherit.Father'
4.要访问子类中特有的方法和属性,在编写代码时,则必须进行类型转换。
class Father{ private int count=10;}class Son extends Father{ private int count=100; public static void main(String[] args) { Son s =new Son(); Son instance=s; System.out.println(instance.count); }}结果 100
改为如下代码:
class Father{ private int count=10;}class Son extends Father{ private int count=100; public static void main(String[] args) { Son s =new Son(); Father instance=s; System.out.println(instance.count); }}instance.count 处直接报错: 'count' has private access in 'reviewInherit.Father'
对于 Class Father 和Class Son来说,Class Son是Class Father的子类,由于子类的变量并不会覆盖父类的变量,所以实际上在class B中是存在来两个count,在这分别记作Father.count
和Son.count
;
虽然在 Class Son中存在A.count
和B.count
,但是究竟输出那一个 count取决于该引用变量的声明时类型
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型可以与父类的放回值类型一样,也可以是父类放回值类型的子类(其他情况报错)。
Object 类属于
java.lang
包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z740UJZM-1632385272613)(D:\document\小林code\小林图片\Object类.png)]
toString()
在使用对象直接嗲用toString()
的时候,默认输出的是一个对象在堆内存上的地址值;如若要输出该对象的内容,则要覆写toString()
方法
在源码中,可以发现通过反射,获取到了当前对象的全限定类名和@十六进制哈希值字符串。这就是不覆写toString()
时直接打印输出的内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNEVGe4Z-1632385272619)(D:\document\小林code\小林图片\原始toString方法.png)]
toHexString()
用于获取double类型的十六进制数字转换为字符串
public static String toHexString(double value);
equals()
只能判断引用类型
基本数据类型的比较用 ==
(如: a == 3,b == 4, a == b,比较的是值是否相等)
引用类型数据比较:调用 equals()
方法进行比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lRpwhdZ-1632385272622)(D:\document\小林code\小林图片\重写equals方法.png)]
hashcode()
Object 对象的 hashCode() 方法会根据不同的对象生成不同的哈希值,默认情况下为了确保这个哈希值的唯一性,是通过将该对象的内部地址转换成一个整数来实现的。
通过计算,返回对象的hash值。相同的对象一定返回相同的hash值,但不同的对象不一定放回不同的hash值,也就是说,不同的对象可能产生相同hash值(hash冲突)。
/* a[]中包含的是对象的属性值*/public static int hashCode(Object a[]) { if (a == null) return 0; int result = 1; for (Object element : a) result = 31 * result + (element == null ? 0 : element.hashCode()); return result; }
通常我们在使用Set和Map集合时,不允许在集合有相同对象。这种机制就是使用hashcode()
的特性来实现的,但由于hash冲突的存在,就要求在重写了hashcode()
后,必须重写equals()
。
equals()
方法来进一步判断对象是否相同。finalize()
当没有任何引用的指向一个对象时,则jvm就认为该对象就是一个垃圾对象,就会使用垃圾回机制来销毁该对象(释放该对象的堆内存空间),在对象销毁前会调用finaliz()
。程序员可以在finalize()方法中写入自己的业务逻辑代码(比如释放资源:关闭数据库连接,关闭文件)
clone()
定义:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
使用拷贝构造函数实现浅拷贝:
使用clone()
实现浅拷贝
定义:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
使用clone()
实现
使用序列化实现深拷贝
clone()
为什么必须实现Cloneable
接口:
Cloneable
是标示接口与java.io.Serializable
类似,用于告知JVM
该对象实现clone
。并且super.clone()
可以返回一个复制。JDK
的文档方法的重载又被称为:静态多态
方法的重写又被称为:动态多态
Animal animal = new Dog();animal= new Cat();
语法:子类类型 引用名 = (子类类型)父类引用
//编译类型和运行类型都是CatCat cat = (Cat)Animal;
要求父类引用必须指向的是当前目标类型的对象
判断对象的运行时类型是否否为xx类型的类型或xx的子类型
在断点调试过程中,程序处于运行状态。也就是说变量是运行时类型
当你在调试的过程中,依然可以断点,使用F9可以跳到下一个断点。但也会根据业务逻辑,来判断是否能到达下一个断点,如果不能,就会跳过。
相当于另一种形式的构造器(对构造器的一种补充机制),可以做初始化操作
当我们每创建一个对象时,都会先调用代码块中的内容。代码块中的调用优先于构造器
创建对象时,在一个类中的调用顺序:
当你调用一个构造器,为了更好理解,可以认为在构造器中隐藏了两段代码,例如:
public Person(){ super(); //本类的普通代码块和普通属性}
public class SingerTon1 { public static void main(String[] args) { GirlFriend girlFriend1 = GirlFriend.getGirlFriend(); GirlFriend girlFriend2 = GirlFriend.getGirlFriend(); System.out.println(girlFriend1==girlFriend2); System.out.println(girlFriend1); }}class GirlFriend { private String name; // static b保证静态方法中返回 private static GirlFriend girlFriend = new GirlFriend("锅盖"); // private 外部不能创建对象 private GirlFriend(String name) { this.name = name; } public static GirlFriend getGirlFriend() { return girlFriend; } @Override public String toString() { return "GirlFriend{" + "name='" + name + '\'' + '}'; }}
饿汉式:如果类中有静态属性,被调用话。就会调用构造器,产生对象,容易造成资源浪费
public class SingerTon2 { public static void main(String[] args) { Dog dog= Dog.getInstance(); System.out.println(dog); }}class Dog{ private String name; private static Dog dog; private Dog(String name){ this.name=name; } public static Dog getInstance(){ if(dog==null){ dog=new Dog("元芳"); } return dog; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; }}
final
final
修饰变量,定义时必须赋值:有如下几种情况
如果final
修饰的是静态属性的话,只能在:
fina
类不能继承,但可以实例化
如果不是final
类,但有final
属性,属性可以被继承,不能被重写
如果一个类已经是final
方法,就没有必要将属性和方法修饰为final了
final
不能修饰构造方法
final
和static
往往搭配使用,效率更高,不会导致类的加载,底层编译器做了优化
包装类String
类都是final
修饰,不能被继承。
接口中可以有三种类型的方法:
细节:
接口是对java单继承的一种补充
public class C { public static void main(String[] args) { A a = new D(); B b = new D(); }}interface A{ void say();}interface B extends A{ void hi();}class D implements B{ @Override public void hi() { System.out.println("你好"); } @Override public void say() { System.out.println("憨憨"); }}
public class E extends F{ public static void main(String[] args) { G g = new E(); }}class F extends G{}class G{}
final
(相当于一个局部变量)外部类.this.属性名
来访问。外部类.this
就表示调用该方法的对象匿名内部类的本质还是一个类,定义在外部类的局部位置。其实也相当于一个对象。
接口的匿名内部类:
public class AnonInter { public static void main(String[] args) { Inner inner = new Inner() { @Override public void say() { System.out.println("hello"); } }; System.out.println(inner.getClass()); //class anonymous.AnonInter$1 }}interface Inner{ void say();}
编译时类型:Inner
运行时类型:AnonInter$1
其实在JDK的底层是会创建一个类(外部类$1
),该类隐式的实现了Inner
接口。创建该类的实例对象,将对象的地址放回。我们可以通过对象名.getClass()
来获取匿名内部类的运行时类型
匿名内部类只能使用一次
类的匿名内部类
public class AnonClass { public static void main(String[] args) { Father father = new Father(){ @Override public void say() { System.out.println("hello"); } }; System.out.println(father.getClass()); //class anonymous.AnonClass$1 }}class Father{ public void say(){ System.out.println("你好"); }}
当我们在new Father()
后加了括号,就不再是创建一个对象了,而是一个匿名内部类,她在底层隐式继承了Father
类,所以可重写父类的方法
编译时类型:Father
运行时类型:AnonClass$1
如果使用匿名内部类时,有参数传入的话,会传给Fathe构造器
public class Anon { public static void main(String[] args) { Base base=new Base(){ @Override public void say() { System.out.println("hello"); } }; base.say(); //hello new Base(){ @Override public void say() { System.out.println("Hello Word"); } }.say(); //Hello Word }}class Base{ public void say(){ System.out.println("你好"); }}
使用细节:
可以直接访问外部类的所有成员
可以使用任意修饰符修饰,因为成员内部类就相当于一个成员属性
作用域:外部类中都可以使用,地位相当于成员属性
成员内部类访问外部类成员的方式:直接访问
外部类访问成员内部类成员的方式:先创建成员内部类对象,通过对象访问成员
外部其他类访问成员内部类的方式:
public class Test { public static void main(String[] args) { Out out = new Out(); Out.Inner inner = out.new Inner(); inner.say(); Out.Inner innerInstance = out.getInnerInstance(); innerInstance.say(); }}class Out{ public class Inner{ public void say(){ System.out.println("hello"); } } public Inner getInnerInstance(){ return new Inner(); }}
外部类类名.this.属性
使用static修饰,放在外部类的成员位置
可以直接访问外部类的所有静态成员,包括私有成员
可以添加任意访问修饰符
作用域:整个外部类类体
外部其他类访问静态内部类
public class Test { public static void main(String[] args) { //外部其他类访问静态内部类成员 //1. 使用外部类名创建对象 Out.Inner inner = new Out.Inner(); //2. 返回对象实例 Out out = new Out(); //使用普通方法 Out.Inner innerInstance = out.getInnerInstance(); //使用静态方法 Out.Inner instance = out.getInstance(); }}class Out{ static class Inner{ public void say(){ System.out.println("hello"); } } public Inner getInnerInstance(){ return new Inner(); } public static Inner getInstance(){ return new Inner(); }}
如果静态内部类和外部类有重名的成员,遵循就近原则。想要在成员内部类中访问外部类的成员,就需要使用外部类类名.属性
@Target
修饰注解的注解,元注解。表明注解可以修饰那些元素
@Retention
可以指定保留的策略
@Documented
使用javadoc工具提取成文件,及生成文档时,可以看到该注解
@interface
表示一个注解类
@Override
表明方法被重写
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
@Deprecated
表明元素已经过时
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public @interface Deprecated {}
@SuppressWarnings
抑制警告
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { /** * The set of warnings that are to be suppressed by the compiler in the * annotated element. Duplicate names are permitted. The second and * successive occurrences of a name are ignored. The presence of * unrecognized warning names is not an error: Compilers must * ignore any warning names they do not recognize. They are, however, * free to emit a warning if an annotation contains an unrecognized * warning name. * * The string {@code "unchecked"} is used to suppress * unchecked warnings. Compiler vendors should document the * additional warning names they support in conjunction with this * annotation type. They are encouraged to cooperate to ensure * that the same names work across multiple compilers. * @return the set of warnings to be suppressed */ String[] value();}
try–catch快捷键(Ctrl+Alt+T)
异常可以分为两大类:运行时异常和编译时异常
编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免的异常。对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
编译时要求必须处理的异常
public class InputInteger { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int num; while(true){ System.out.println("请输入一个整数"); try { num = Integer.parseInt(scanner.next()); break; } catch (NumberFormatException e) { System.out.println("你的输入有误"); } } System.out.println("你输入的数字为:"+ num); }}
创建一个类继承RuntimeException类即可。我们一般都将其定义为一个运行时异常,可以采用默认的处理机制,使用起来比较方便。
意义 | 位置 | 后跟 | |
---|---|---|---|
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
throws | 异常处理的一种方式 | 方法声明中 | 异常类型 |
有了包装类对象,就可以使用类的方法
Integer integer = 20; //自动装箱 底层调用Integer.Valueof(20)int n = integer; //自动拆箱 底层调用Integer.intValue(integer)
题目练习
Object obj = true? new Integer(1): new Double(1);//输出 1.0//因为三元运行符是一个整体,会提升优先级
if(true){ System.out.println(new Integer(1));}else{ System.out.println(new Double(1));}//输出1
只要有基本数据类型比较,就是是值的比较
Integer i1 = 128;int i2 =128;System.out.println(i1==i2); //ture
private final char value[]
用于存放字符串内容,value[]
被final修饰,value的指向就不可以修改了,但value[]
中内容是可以修改的。String str = "hsp";
先看常量池中是否有hsp
,如果有,主栈中的str引用变量就直接指向常量池中hsp
,如果没有就先创建,再指向
Sting str = new Integer("hsp");
现在堆中创建一个内存空间,里面维护value
属性,指向常量池中的hsp
。如果在常量池中没有hsp
,先创建,再指向。主栈中的str指向堆中创建的内存空间。
当调用intern()
方法时,如果包含一个等于此String对象的字符串(用equal()
确定),这返回池中字符串的地址。否则将String对象的字符串添加到常量池中,并返回常量池中引用。最终返回的都是常量池中的地址
String-->char[]
String str = "zzh";char[] chars = str.toCharArray();
compareTo()
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
String str = "a";str = "b"; //常量池中创建了两个对象:改变了str的指向
String a = "hello" + "abc";//创建一个对象:编译器进行了优化//等价于 String a = "helloabc";
String a = "hello";String b ="abc";String c = a + b;/* 1.先创建一个 StringBurder sb = new StringBurder(); 空对象 2.执行 sb.append("hello"); 3.sb.append("abc"); 4.String c = sb.toString(); 最后其实c指向的是堆中的(String)对象 value[],value[] ->池中"helloabc"*/
常量相加,看的是池。变量相加,看的是堆。
class Test{ String str = new String("hsp"); final char[] ch = {'j','a','v','a'}; public void change(String str,char[] ch){ str = "java"; ch[0] = 'h'; }}class Test3{ public static void main(String[] args) { Test test = new Test(); test.change(test.str, test.ch); System.out.print(test.str + " and "); System.out.println(test.ch); }}//只要有一个方法在栈中就会创建一个帧,当该方法用完,就会出栈//最后输出hsp and hava
内存图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZF7mmOrt-1632385272625)(D:\document\小林code\小林图片\字符串内出图.png)]
char[]
value,不是final,该value数组存放字符串内容String保存的是字符串常量,里面的值不能修改,每次内容的更新,其实是引用变量指向一个新的引用变量,效率较低
StringBuffer保存的字符串的变量,里面的值可以更改,每次内容的更新,就是value[]
数组内容的改变,效率较高
-->
StringBufferString str = "hello";//使用构造器 放回的是一个StringBuffer对象,对str本身没有影响StringBuffer sb = new StringBuffer(str);//使用append()StringBuffer sb1 = new StringBuffer();sb1 = sb1.append(str);
-->
String//使用StringBuffer提供的toString()StringBuffer sb2 = new StringBuffer();String str = sb2.toString();//使用构造器String str1 = new String(s)
String str = null;StringBuilder sb = new StringBuilder();sb.append(str);System.out.println(sb.length()); //sb为null,输出4StringBuffer sb1 = new StringBuffer(str);System.out.println(sb1.length());//抛出空指针异常
StringBuffer中有insert()
,经常用于插入字符串
StringBuilder是StringBuffer的一个建议替换,存在线程安全问题,常用于单线程,速度比StringBuufer快
char[]
value,因此字符序列在堆结论:如果我们要对字符串进行大量修改,尽量不要使用String
public static void main(String[] args) { rand(1,10);}//生成[a,b]的随机数public static void rand(int a, int b){ for (int i = 0; i < 10; i++) { int ran = (int)(Math.random()*(b-a+1)+a); System.out.println(ran); }}
toString()
返回数组的字符串形式
Arrays.toString(arr)
sort排序(自然排序和定制排序)
Arrays.sort(arr)Arrays.sort(arr, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } });
binarySearch 通过二分查找索引法进行查找,要求必须排序。
return -(low + 1)
copyOf 数组的复制
int[] newArr = Arrays.copyOf(arr,arr.length);int[] newArr = Arrays.copyOf(arr,arr.length + 1);//如果拷贝的长度 > arr.length 就在新数组中的后面,增加nullint[] newArr = Arrays.copyOf(arr,arr.length - 1);//如果拷贝的长度 < 0 就会抛出异常NegativeArraysSizeException
fill()
Arrays.fill(num,99);//使用99替换num数组的元素
equals()
比较数组元素内容是否一致
Arrays.equals(arr1,arr2)
asList()
将数组转为List集合
List list = Arrays.asList(arr);// 运行时类型java.util.Arrays#ArraysList /Arrays类中的一个内部类
exit()
: 退出当前程序。参数一般为0,表示正常退出
arryaycopy()
:复制数组元素,比较适合底层调用。一般使用Arrays.copyOf() 来完成数组调用
/*src – the source array.srcPos – starting position in the source array.dest – the destination array.destPos – starting position in the destination data.length – the number of array elements to be copied*/public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
currentTimeMillens()
:放回当前时间距离1970-1-1的毫秒数
gc()
: 运行垃圾回收机制
BigInteger bigInteger = new BigInteger("29999999999999999999");BigInteger bigInteger1 = new BigInteger("99999");//加法 :参数类型和放回值都是BigIntegerBigInteger add = bigInteger.add(bigInteger1);//减法BigInteger subtract = bigInteger.subtract(bigInteger1);//乘法BigInteger multiply = bigInteger.multiply(bigInteger1);//除法BigInteger divide = bigInteger.divide(bigInteger1);
BigDecimal bigDecimal = new BigDecimal("19.99999999999999999297");BigDecimal bigDecimal1 = new BigDecimal("17");System.out.println(bigDecimal.divide(bigDecimal1));//Non-terminating decimal expansion; 这里肯会抛出异常//处理方法System.out.println(bigDecimal.divide(bigDecimal1, RoundingMode.CEILING));//保留小数位数和分子的小数位数相同
public Date(long date) { fastTime = date;}public Date() { this(System.currentTimeMillis());}//获取当前系统时间 Date date = new Date(); //得到当前时间的毫秒数 System.out.println(date.getTime()); //设置日期输出的格式 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss"); System.out.println(simpleDateFormat.format(date)); //2021年09月07日 10:22:12 //字符串转为日期格式 Date parse = simpleDateFormat.parse("2021年09月07日 10:22:12"); System.out.println(simpleDateFormat.format(parse));
Caldedar类是一个抽象类,并且构造器是是私有的
可以通过getInstance()
来获取对象实例
提供的大量的字段和方法
Calendar instance = Calendar.getInstance();System.out.println(instance);
Cladedar类在返回月时,是按照0开始编号
没有提供日期格式的方法,可以自由灵活的定义日期格式
class TestLocalDate{ public static void main(String[] args) { LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); System.out.println(localDateTime.getYear()); System.out.println(localDateTime.getMonth()); System.out.println(localDateTime.getMonthValue()); System.out.println(localDateTime.getDayOfMonth()); System.out.println(localDateTime.getHour()); System.out.println(localDateTime.getMinute()); System.out.println(localDateTime.getSecond()); //只包含年月日 LocalDate localDate = LocalDate.now(); System.out.println(localDate.getYear()); //只包含时分秒 LocalTime localTime = LocalTime.now(); System.out.println(localTime.getHour()); //日期格式化 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm:ss"); System.out.println(dateTimeFormatter.format(localDateTime)); }}class TestInstant{ public static void main(String[] args) { //通过静态方法 now() 获取当前时间戳对象 Instant now = Instant.now(); System.out.println(now); //通过from 把Instant转为Date Date date = Date.from(now); //通过date的toInstant() Date转为Instant Instant instant = date.toInstant(); }}
集合的优点:
集合主要分为:单例集合,双列集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SllO7j4s-1632385272627)(D:\document\小林code\小林图片\Collection继承图.png)]
Iterator对象称为迭代器,主要用于遍历Collection集合中元素
Collection接口继承了Iterator接口,所以每个实现了Collection的集合,都有一个iterator()
方法,该方法放回一个Iterator对象,即放回一个迭代器
Iterator 仅用于遍历集合,Iterator本身不存放对象
hasnext()
判断该是否有下一个元素
next()
:指针移向下一个元素,并放回该元素。
使用Iterator
遍历集合
Iterator iterator = list.iterator();while (iterator.hasNext()) { Object next = iterator.next();}/* 当退出while时,iterator迭代器,指向最后一个元素,如果想要再次遍历,需要重置*/iterator = list.iterator();
使用增强for遍历集合(可代替Iterator,简化版的迭代器)
for (Object o : list) { System.out.println(o);}
List 集合中的元素有顺序(即添加顺序和输出顺序相同),且可重符
List集合中每个元素都有器对应的顺序索引,及支持索引
System.out.println(list.get(2));
List 集合还可以使用普通for进行遍历
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}
ArrayList中维护的是一个Object[] elementData
transient Object[] elementData;
创建ArrayList对象是,使用无参构造器,初始容量为0,第一次添加时扩容为10,如以后还需要扩容,则扩容为原来的1.5倍
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
使用有参构造器,可以指定ArrayList的初始容量,以后需要扩容时,扩容为原来的1.5倍
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
扩容机制
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
public class Vector<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
底层是一个Object
对象数组
protected Object[] elementData;
Vector是线程安全的,Vector类的操作方法都带有synchronized
关键字
底层实现了双向链表和双端队列
可以添加任何元素,包括null
第一个节点的prev = null
,最后一个节点的next = null
线程不安全
常用方法:
LinkedList linkedList = new LinkedList();linkedList.add(999);linkedList.remove();linkedList.get(1);linkedList.set(0,999);
HashSet底层其实是HashMap
public HashSet() { map = new HashMap<>(); }
在HashSet的底层有一个HashMap对象private transient HashMap
成员变量,private static final Object PRESENT = new Object();
,该成员变量的作用就是为了HashSet
可以使用HashMap
,而占位用的
/*The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)表,在第一次使用时初始化,并根据需要调整大小。 分配时,长度始终是 2 的幂。 (我们还在某些操作中容忍长度为零,以允许当前不需要的引导机制。)*/transient Node<K,V>[] table;
putVal()
源码分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量 //table 就是 transient Node[] table; //if表示如果当前table 是 null 或容量为0.就进行第一次扩容,容量大小为16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; /*根据hash值类确定元素应存放在table的那个位置,并把这个位置的地址赋值给p,判断p是否为空 如果为空,就表明该位置还没有存放元素,就创建一个节点,这里把hash值也传了进去,用于下一次存 放元素时,进行比较是否相同 */ if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //当table表中索引位置已经有元素存在,就进入到else块中 else { Node e; K k; /* 该对象的hash值和该索引位置的第一个对象的hash值相同&& (引用变量key相同 || 内容相同) */ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); else { //这里会让要添加的元素和当前索引下的元素依次进行比较 for (int binCount = 0; ; ++binCount) { //如果没有相同的元素,将元素添加进去,退出循环 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); /* 再把元素添加到链表后,就立即判断是否达到了8个节点,如果达到就调用 treeifyBin(tab, hash);将链表转换为一个红黑树。 在treeifyBin(tab, hash)中,还要进行判断: if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); 即table的长度小于64时,先要对table进行扩容 */ if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //如果发现有相同的元素,直接退出循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; //移向下一个元素,继续进行比较 p = e; } } //存在相同的key,ji if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }//最后返回的结果如果为null,就代表添加元素成功,否则,添加失败
resize()
源码分析
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; //临界值:表示当数组的内容个数达到该值,就要扩容 int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults //static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认容量大小16 newCap = DEFAULT_INITIAL_CAPACITY; //static final float DEFAULT_LOAD_FACTOR = 0.75f; 装载因子 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node[] newTab = (Node[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode)e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
HashSet底层第一次添加元素时,table数组扩容为16,临界值为16*0.75=12
如果数组中的元素个数(不管该元素加在哪里,只要加入到HashSet中就算)到达了临界值,就会扩容为原来的2倍
当一条链表的个数达到8,并且table的容量>=64时,才会将链表转换为红黑树。如果一条链表的个数达到了8,但table容量<64,会对table进行扩容,扩容还是按照扩容到原来的二倍。
LinkedHashSet是HashSet的一个子类
LinkedHashSet底层是一个LinkedHashMap,维护一个数组(table) + 双向链表
LinkedHashSet根据元素的hash值来决定元素存储的位置,同时使用链表维护元素的顺序,这使元素看起来是以插入顺序保存的
LinkedHashSet不允许有重复元素
第一次添加元素时,直接将数组table
的容量扩容到16,存放的节点类型是:LinkedHash’Map$Entry
数组table
是 HashMap$Node[]
,存放的元素是 LinkedHashMap$Entry类型
//Entry 是在LinkedHashMap中的一个静态内部类static class Entry extends HashMap.Node { Entry before, after; Entry(int hash, K key, V value, Node next) { super(hash, key, value, next); } }
Map用于保存具有映射关系的数据:Key–Value(双列元素)
Map中的Key和Value可以是任何引用类型的数据,会封装到HashMap$Node 对象中
transient Node<K,V>[] table;
Map中的可以不允许重复,原因和HashSet一样。当有相同的key是,就会替换原来的Value
Map中Value是可以重复的
Map中的Key可以为null,Value也可以为null,当Key为空只能有一个,Value为空可以有多个
常用String类做Key值
我们总能通过Key找到对应的Value
HashMap没有首先线程同步,所以线程是不安全的
k-v首先是存放在一个Node中
//HashMap中的静态内部类 static class Node implements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
通过newNod()
返回以Node对象
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next); }
放入table数组中
tab[i] = newNode(hash, key, value, null);
k-v 为了方便程序员的遍历,还会创建entrySet的Set集合,该集合存放的元素类型 Entry
//HashMap中的一个静态内部类final class EntrySet extends AbstractSet>//Map接口中内部接口interface Entry;//HashMap中成员transient Set> entrySet;//HashMap中方法public Set> entrySet() { Set> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; }
entrySet中定义类型是Map.Entry,但实际存放的还是 HashMap$Node
static class Node<K,V> implements Map.Entry<K,V>
当把 HashMap$Node对象存放到entrySet中就会方便我们遍历,因为Map.Entry提供了重要的方法
K getKey();V getValue();
需要注意的是:在entrySet只是指向了Node,并没有真正的存放数据
我们还可以得到Key和Value的Set集合
Set set = hashMap.keySet(); //运行时类型:class java.util.HashMap$KeySetCollection values = hashMap.values(); //运行时类型:class java.util.HashMap$Values
final class KeySet extends AbstractSet<K>final class Values extends AbstractCollection<V>
HashMap hashMap = new HashMap();hashMap.put("zzh","guogai");hashMap.get("zzh");hashMap.size();hashMap.isEmpty();hashMap.containsKey("zzh");hashMap.containsValue("guogai");hashMap.remove("zzh");hashMap.clear();
Set entrySet = hashMap.entrySet();System.out.println(entrySet.getClass()); //class java.util.HashMap$EntrySetfor (Object entry : entrySet) { Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey()+" - "+m.getValue());}
Set keySet = hashMap.keySet();for (Object key :keySet ) { System.out.println(key + " - " + hashMap.get(key));}Iterator iterator = keySet.iterator();while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next + " - "+ hashMap.get(next));}
Collection values = hashMap.values();for (Object value : values) { System.out.println(value);}Iterator iterator1 = values.iterator();while (iterator1.hasNext()) { Object next = iterator1.next(); System.out.println(next);}
存放的元素时键值对:k–v
Hashtable的键值不能为null,否则或抛出空指针异常
Hashtable的使用方法,基本和HashMap一样
Hashtable是线程安全的(synchronized),HashMap是线程不安全的
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
底层有一个数组 private transient Entry,?>[] table;
初始容量:11,加载因子:0.75,临界值:8
扩容机制
int newCapacity = (oldCapacity << 1) + 1;
Properties 继承了 Hashtable,具有Hashtable的特点
Properties 主要用于读取配置文件
常用方法
Properties properties = new Properties();properties.put("1","zzh");properties.put("2","锅盖");System.out.println(properties.get("1"));properties.remove("1");properties.put("2","guogai");System.out.println(properties);
底层是TreeMap
构造器可传入一个比较器对象,赋值给TreeSet底层TreeMap的属性this.comparator
底层比较的执行机制
Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); }
从底层的代码可以知道:当compareTo()
放回0时,就认为这两个数据相同,就不会加入到TreeSet中了
底层维护一个private transient Entry
采用红黑树的数据结构
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK; /** * Make a new cell with given key, value, and parent, and with * {@code null} child links, and BLACK color. */ Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; }
添加元素机制
public V put(K key, V value) { Entry<K,V> t = root; //第一次添加元素,直接将元素挂在root上去 if (t == null) { //这里调compare方法是为了检查第一次传经来的值是否为null,如果为 compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry parent; // split comparator and comparable paths Comparator super K> cpr = comparator; //通过循环来确定要加入元素在树中的位置 if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //如果通过compareTO()比较有相同的key值,会发生替换 return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable super K> k = (Comparable super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
如果通过compareTO()比较有相同的key值,会发生替换
ArrayList arrayList = new ArrayList(); arrayList.add("zzh"); arrayList.add("erha"); arrayList.add("guogai"); arrayList.add("yuanfang"); //反转list Collections.reverse(arrayList); //随机排序 Collections.shuffle(arrayList); //按照自然排序 Collections.sort(arrayList); //指定排序规则排序 Collections.sort(arrayList, new Comparator
泛型是可以表示数据类型的一种数据类型
泛型又称参数化类型,解决数据类型安全性的问题
在类声明或实例化时只要指定好具体的类型即可
Java泛型可以保证在编译时没有发出警告,在运行时就不会产生ClassCastException异常,同时,代码更加简洁,健壮
泛型的作用:可以在类声明时,通过泛型表示类中某个属性的类型,或者方法的返回值类型,或者参数类型
class Person<E>{ private E name; public Person(E name) { this.name = name; } public E getName(){ return name; } public void setName(E name){ this.name = name; }}
泛型的使用
HashSet<Student> students = new HashSet<>(); students.add(new Student("zzh",18)); students.add(new Student("guogai",19)); students.add(new Student("yuanfang",20)); Iterator<Student> iterator = students.iterator(); while (iterator.hasNext()) { Student student = iterator.next(); System.out.println(student); } for (Student student :students) { System.out.println(student); } //在我们定义HashMap对象时,就已经确定了泛型的类型,之后Iterator中泛型类型也是已经确定的 HashMap hashMap = new HashMap(); hashMap.put("zzh",new Student("zzh",18)); hashMap.put("guogai",new Student("guogai",19)); hashMap.put("yuanfang",new Student("yuanfang",20)); Set> entries = hashMap.entrySet(); Iterator> entryIterator = entries.iterator(); while (entryIterator.hasNext()) { Map.Entry studentEntry = entryIterator.next(); System.out.println(studentEntry.getKey()+ " - "+studentEntry.getValue()); }
泛型(T,E,R,M)只能是引用类型,不能为基本数据类型
在给泛型指定具体类型后,可以传入该类型和该类型的子类型
泛型的写法
HashSet<Student> students = new HashSet<Student>();//也可以简写,编译器会进行类型推断HashSet students = new HashSet<>();//如果没有给泛型指定具体的类型,默认是ObjectHashSet students = new HashSet();HashSet students = new HashSet<>();
JVM
就无法完成初始化)class Person<E>{ private E name; public Person(E name) { this.name = name; } public E getName(){ return name; } public void setName(E name){ this.name = name; }}
public interface GenericInter<T,K> { T method(T name,K age);}
泛型方法可以定义字普通类中,也可以定义在泛型类中
class People{ private String name; private int age; public <K,V> void setName(K k,V v ){ System.out.println(k.getClass()); System.out.println(v.getClass()); }}public class Person<T,K> { public static void main(String[] args) { } public<U,M> void method(U u,M m){ }}
泛型方法在调用时,泛型类型就会确定下来,编译器会根据传入的参数来确定泛型类型
泛型方法可以使用类声明的方法,也可以使用自己声明的泛型
修饰符后没有使用泛型的方法,不是泛型方法。只是使用了泛型类的泛型
//不是泛型方法public void method(T t){ }
当确定泛型类型时,不能有继承的关系
//这样写是错的ArrayList objects = new ArrayList();
泛型的通配(用在方法中约束参数的类型)
>
:支持任意泛型 extends A>
:支持A类,即A类的子类,规定了泛型的上限(上限为A类) super A>
:支持A类,即A类的父类,规定了泛型的下限(下限为A类)程序:简单来说,就是我们写的代码
进程:运行中的程序,一个运行中的程序,就是一个进程。进程是程序的一次执行过程,或正在运行的一个程序。是动态的过程:有它自身的产生,存在和消亡的过程
线程:线程是由进程创建的,是进程的一个实体,一个进程可以允许有多个线程
并发:同一时刻,多个任务交替执行,造成一种同时运行的错觉,简单来说,单核CPU实现多任务的并发
并行:同一时刻,多个任务同时运行。多个CPU可实现并行
当一个类继承了Thread类后,该类就可以当作线程类使用
通常会重写run()
,写上自己的业务代码。run()
定义在Runable
接口中
Thread类中的run()
@Override public void run() { if (target != null) { target.run(); } }
public class TestThread { public static void main(String[] args) { Cat cat = new Cat(); cat.start(); }}class Cat extends Thread{ @Override public void run() { System.out.println("Hello Thread"); }}
当我们在IDEA中点击Run运行一个程序的时候,就相当于启动了一个进程,然后我们找到main()
方法程序入口就相当于进程创建了一个子线程(main线程
)
cat.start()
相当于main线程
启动了cat
线程
当main线程创建了cat线程后,不会阻塞main线程
**当我们所有的线程都运行结束,程序(进程)才会退出运行。**并不是主线程结束后,进程就结束了。
为什么启动线程不直接调用
run()
,而是调用start()
当我们调用cat.run()
,此时的run()就相当于一个普通的方法调用。调用该方法的的线程是main线程
,整个执行的过程,相当于串行执行,不会交替进行。只有调用了start()方法后,才是真真的启动了一个线程。
public synchronized void start(){ start0();}/*start0() 是一个本地方法,是由JVM调用的真正事项多线程的是start0(),而不是run()*/private native void start0();
public class TestThread { public static void main(String[] args) { Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); }}class Dog implements Runnable{ @Override public void run() { System.out.println("Hello "); }}
在java中是单继承机制,当一个类继承了其他类,就不能继承Thread类。所以提供了实现Runable接口的线程创建方式,这里采用的代理模式(静态代理)
在Runable接口中是没有start()方法的,要采用以下方式:
Dog dog = new Dog();Thread thread = new Thread(dog);thread.start();public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
静态代理的简单实现
public class ThreadProxy implements Runnable { private Runnable target = null; @Override public void run() { if(target != null){ target.run(); } } public void start(){ start0(); } private void start0(){ run(); } public ThreadProxy(Runnable target) { this.target = target; }}class Tiger implements Runnable{ public static void main(String[] args) { Tiger tiger = new Tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start(); } @Override public void run() { System.out.println("HELLO"); }}
yield()
:让出CPU,让线程开始争夺CPUjoin()
:必须先把该线程执行完,再执行其他线程public class TestMethod { public static void main(String[] args) throws InterruptedException { Person1 person1 = new Person1(); Thread thread = new Thread(person1); thread.setName("zzh"); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); System.out.println("主线程休息1秒"); for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(100); if(i == 5){ thread.join(); } } thread.interrupt(); }}@SuppressWarnings({"all"})class Person1 implements Runnable{ @Override public void run() { boolean loop = true; int count = 0; while(loop){ System.out.println("HELLO" +Thread.currentThread().getName()+ " "+(++count)); try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println("线程被中断了"); } if (count == 10){ break; } } }}
用户线程:当线程的任务完成,或通知线程结束,线程就会结束
守护线程:一般为工作线程服务,当所有的用户线程执行结束,守护线程会自动结束。常见的守护线程:垃圾回收机制
thread.setDaemon(true);
-->
false,达到通知线程的效果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6N6Jn3t1-1632385272629)(D:\document\小林code\小林图片\线程七大状态.png)]
/* public synchronized void sell(){} 就是一个同步方法,这时的对象锁为this */ public synchronized void sell(){ if(count<=0){ System.out.println("售票结束"); loop = false; return; } System.out.println(Thread.currentThread().getName()+"售出第"+(count--)+"张票"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //和上面的效果是一样的 public void sell(){ synchronized(this) { if (count <= 0) { System.out.println("售票结束"); loop = false; return; } System.out.println(Thread.currentThread().getName() + "售出第" + (count--) + "张票"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } //静态方法的代码互斥锁 public static void method(){ synchronized (SellTicket.class){ System.out.println("HELLO"); } }
可以释放锁的操作:
wait()
,当前线程暂停,并释放锁下面的操作不会释放锁:
Thread.sleep()
,Thread.yield()
方法 暂停当前线程的执行,不会释放锁supend()
方法将该线程挂起,该线程不会释放锁输入流和输出流是相对于内存而言的
/*new File() 只是在内存中创建了File对象真正的创建文件到磁盘,必须要调用createNewFile() 方法*/File file = new File("d:\\document\\new.txt");file.createNewFile();File file1 = new File("d:\\document\\", "new1.txt");file1.createNewFile();
File file = new File("D:\\document\\new.txt");System.out.println(file.getName());System.out.println(file.getAbsolutePath());System.out.println("文件的大小"+file.length());System.out.println(file.exists());System.out.println(file.getParent());System.out.println(file.isFile());System.out.println(file.isDirectory());file.delete();//目录操作File file1 = new File("D:\\document\\demo\\a");//创建多级目录:可以同时创建多个目录if(file1.mkdirs()){ System.out.println("创建成功");}else{ System.out.println("创建失败");}file1.delete();//file1.mkdir():创建但级目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GX8BkBoC-1632385272630)(D:\document\小林code\小林图片\IO体系图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmnumHst-1632385272632)(D:\document\小林code\小林图片\InputStream体系图.png)]
public void readFile() throws IOException { File file = new File("D:\\document\\new1.txt"); FileInputStream fileInputStream = new FileInputStream(file); int readDate = 0; //fileInputStream.read() 返回的其实是ASCLL码值 while((readDate = fileInputStream.read()) != -1){ System.out.print((char)readDate); } fileInputStream.close(); }public void readFile1() throws IOException { File file = new File("D:\\document\\new1.txt"); FileInputStream fileInputStream = new FileInputStream(file); int readDate = 0; byte[] buf = new byte[8]; //fileInputStream.read() 返回实际读取的字节数 //返回-1 表示读取完毕 while((readDate = fileInputStream.read(buf)) != -1){ System.out.print(new String(buf,0,readDate)); } fileInputStream.close(); }
public void writeFile() throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("D:\\document\\new1.txt"); //方式一 fileOutputStream.write('Z'); //方法二 String str = "Hello,Word"; fileOutputStream.write(str.getBytes(StandardCharsets.UTF_8)); //方法三 fileOutputStream.write(str.getBytes(StandardCharsets.UTF_8),0,str.length()); //上面的三种方式都会覆盖原来的内容,实现添加的方式: FileOutputStream fileOutputStream1 = new FileOutputStream("D:\\document\\new1.txt",true); fileOutputStream.close();}
节点流:可以从某个特定的数据源(存放数据的地方)读取数据,如FileReader,FileWiter,FileInputStream,FileOutputStream,CharArrayRead(数组的节点流),PipedRead(管道节点流)。
处理流(包装流):对节点流进行包装,相当于一种缓冲。可以对任意的节点流进行包装
//构造器就是传入一个Witerpublic BufferedWriter(Writer out) { this(out, defaultCharBufferSize); }//public BufferedReader(Reader in) { this(in, defaultCharBufferSize); }
public class Adapter { private Reader reader; public void setReader(Reader reader) { this.reader = reader; } public Reader getReader() { return reader; } public static void main(String[] args) { Adapter adapter = new Adapter(); StringReader stringReader = new StringReader(); FileReader fileReader = new FileReader(); adapter.setReader(stringReader); adapter.getReader().readString(); adapter.setReader(fileReader); adapter.getReader().readFile(); }}abstract class Reader{ public void readString(){} public void readFile(){}}class StringReader extends Reader{ @Override public void readString() { System.out.println("读取字符串"); }}class FileReader extends Reader{ @Override public void readFile() { System.out.println("读取文件"); }}
处理流的功能主要体现在以下两个方面
BufferedReader是以字符为单位读取的,所以一般用于文本文件的读取。不适合二进制文件的读取。
关闭流时,只要关闭外层流即可,底层代码如下:
public void close() throws IOException { synchronized (lock) { if (in == null) return; try { //这里的关闭了底层流 in.close(); } finally { in = null; cb = null; } } }
@Test public void testBufWriter() throws IOException { FileWriter fileWriter = new FileWriter("D:\\document\\new2.txt",true); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write("Hello,zzh"); bufferedWriter.newLine(); bufferedWriter.write("Hello,guogai"); bufferedWriter.close(); }
true
bufferedWriter.newLine();
实现换行public void PicCopy() throws IOException { FileInputStream fileInputStream = new FileInputStream("D:\\document\\小林code\\小林图片\\InputStream体系图.png"); FileOutputStream fileOutputStream = new FileOutputStream("D:\\document\\小林code\\Copy.png"); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); byte[] bytes = new byte[1024]; int len; while((len = bufferedInputStream.read(bytes)) != -1){ bufferedOutputStream.write(bytes,0,len); } bufferedInputStream.close(); bufferedOutputStream.close(); }
public class TestObjectStream { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("D:\\document\\data.dat"); ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream); oos.writeInt(100); oos.writeBoolean(true); oos.writeDouble(9.9); oos.writeUTF("HELlO,周志浩"); oos.writeObject(new Dog("erha",10)); oos.close(); } @Test public void Unser() throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("D:\\document\\data.dat"); ObjectInputStream ois = new ObjectInputStream(fis); System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); System.out.println(ois.readObject()); }}class Dog implements Serializable{ private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
System.in 是System类中的 public final static InputStream in = null;/* 编译类型:InputStream 运行类型:BufferedInputStream 表示的是标准的输入 键盘*/System.out 是System类中的 public final static PrintStream out = null;/* 编译类型:PrintStream 运行类型:PrintStream 表示的是标准的输出 显示器*/Scanner scanner = new Scanner(System.in);String next = scanner.next();System.out.println(next);
当处理纯文本数据时,使用字符流的效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流。可以在使用指定编码的方式(比如:UTF-8,GBK)
打印流只有输出流,没有输入流,构造器中可以传入文件,字符串,OutputStream。也就是说可以将数据打印到多个位置
PrintStream out = System.out;//在默认情况下,PrintStream输出的数据的位置:标准输出,即显示器out.print("HELLO");//因为print的底层就是一个writer,所以可以直接调用writer 进行打印输出out.write("HELLO".getBytes(StandardCharsets.UTF_8));System.setOut(new PrintStream("D:\\document\\new.txt"));/** 不能使用上面的out输出,和System.out 不是同一个对象* */System.out.print("HELLO");out.close();
PrintWriter printWriter1 = new PrintWriter(System.out);printWriter1.write("你好,zzh");PrintWriter printWriter = new PrintWriter("D:\\document\\new.txt");printWriter.write("你好,zzh");//真正将数据写入的方法printWriter.close();
专门用来读写配置文件的集合类
配置文件格式
键=值键=值
键值对不需要有空格,值不需要引号引起来。默认类型为String
//创建对象Properties properties = new Properties();//加载指定配置文件properties.load(new FileReader("src\\mysql.properties"));//把k-v显示在控制台properties.list(new PrintStream(System.out));//根据key,获取对应的值String user = properties.getProperty("user");String pw = properties.getProperty("pw");System.out.println("用户为:"+user);System.out.println("密码为:"+pw);Properties properties = new Properties();/* * 创建键值对 * 如果文件中没有key,就是创建 * 有key,就是修改 * */properties.setProperty("user","周志浩");properties.setProperty("psw","10086");properties.setProperty("charset","utf-8");properties.setProperty("psw","8848");/* * 将k-v存储在文件中 * 使用字节流 FileWriter 写入文件的,汉字存储的时unicode码 * 使用字符流 FileOutputStream 写入文件,汉字存储的是汉字 * */properties.store(new FileWriter("src\\mysql2.properties"),null);properties.store(new FileOutputStream("src\\mysql2.properties"),null);System.out.println("保存文件成功");
//字节流的处理方式int n = 0; byte[] bytes = new byte[1024]; while((n = fileInputStream.read(bytes)) != -1){ fileOutputStream.write(bytes,0,n); }//字符流的处理方式int data = 0; char[] buf = new char[8]; while((data = fileReader.read(buf)) != -1){ System.out.print(new String(buf,0,data)); }//缓冲流的处理方式String str = ""; while((str = br.readLine()) != null){ bw.write(str); bw.newLine(); }
反射机制允许在执行期间借助Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法),并能操作对象的属性和方法。反射在设计模式和框架底层都会用到
加载完类之后,就会在堆中产生一个Class类型的对象(一个类只有一个Class对象,也只会加载一次),这个对象包含了类的完整结构信息。通过Class类的对象得到类的结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGHKczWH-1632385272634)(D:\document\小林code\小林图片\Java程序运行三阶段.png)]
优点:可以动态的创建和使用对象(也是框架的底层核心),使用灵活
缺点:使用反射基本是解释执行,对执行速度有影响
Filed,Method,Constructor对象都有setAccessible()
,该方法是启动或禁用访问安全检查的开关,参数为true时,表示对象访问时取消安全检查情况。(优化效果不是特别明显)
Class类也是类,因此也继承Object类
Class类不是new出来的,而是系统创建的。有ClassLoader类中的loadClass(String name)
创建
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }
对于某个类的Class对象,在内存中只有一份,因为类只加载一次
每个类的对象都会记得自己是由那个对象实例生成的
通过Class对象的各种API,可以完整得到一个类的完整结构
Class对象存放再堆中
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KlAj5eBH-1632385272635)(D:\document\小林code\小林图片\继承内存关系图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCoSikz2-1632385272636)(D:\document\小林code\小林图片\类加载内存.png)]
String fullpath = "reflection.Person";Class<?> aClass = Class.forName(fullpath);//输出aClass对象,属于那个了类 class reflection.PersonSystem.out.println(aClass);//输出aClass对象的运行类型System.out.println(aClass.getClass());//得到包名System.out.println(aClass.getPackage().getName());//创建类的对象实例Person person = (Person) aClass.newInstance();System.out.println(person);//通过反射获取属性 该方法不能获取私有属性Field name = aClass.getField("name");//public java.lang.String reflection.Person.nameSystem.out.println(name);//nameSystem.out.println(name.getName());//zzhSystem.out.println(name.get(person));//通过反射设置属性值name.set(person,"erha");System.out.println(name.get(person));Field[] fields = aClass.getFields();for (Field f : fields) { System.out.println(f.getName());}//无参构造器Constructor> constructor = aClass.getConstructor();System.out.println(constructor);//有参构造器Constructor> constructor1 = aClass.getConstructor(String.class, int.class);System.out.println(constructor1);
//应用场景:配置文件String fullpath = "reflection.Person";Class> cls1 = Class.forName(fullpath);System.out.println(cls1.hashCode());//应用场景:传入参数Class cls2 = Person.class;System.out.println(cls2.hashCode());//应用场景:有对象实例Person person = new Person();Class extends Person> cls3 = person.getClass();System.out.println(cls3.hashCode());//通过类加载器获取类对象//(1) 先得到类加载器ClassLoader classLoader = person.getClass().getClassLoader();Class> cls4 = classLoader.loadClass(fullpath);System.out.println(cls4.hashCode());//基本数据类型获取Class类Class integerClass = int.class;Class characterClass = char.class;//数据类型对应的包装类,可以通过.TYPE的Class对象Class type = Integer.TYPE;Class type1 = Character.TYPE;
静态加载:编译时加载相关的类,也就是说编译时(转为字节码文件)就会把你代码中用到的类进行加载,如果没有就会报错,依赖性太强
动态加载:运行时加载需要的类,如果允许时没有用到该类,即使该类不存在,则不会报错,降低了依赖性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pA8Bte0C-1632385272638)(D:\document\小林code\小林图片\类加载过程图.png)]
loadClass(String name)
进行加载的java.lang.Class
对象连接阶段,其实就是将类的二进制数据合并到JRE中,此时,已经是一个可执行文件
给静态变量分配内存并且完成默认初始化
/** n1 是实例属性,不是静态变量 ,因此在准备阶段,是不会分配内存的* n2 是静态变量,在准备阶段,分配内存n2,默认初始化为 0* n3 是静态常量,他和静态变量不同,一旦复制就不会在变,所以默认初始化为 40* */private int n1 = 20;private static int n2 = 30;private static final int n3 = 40;
clint<>()
是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值和静态代码块中的语句,并进行合并clint<>()
会有同步的过程,保证同一时刻只有一个线程加载该类。正是因为这个机制才能保证在内存中只有一个Class类对象最后内存的分布情况:
加载阶段和连接阶段是由JVM来负责的,初始化阶段程序员时可以控制的
String fullpath = "reflection.Cat";Class<?> catClass = Class.forName(fullpath);//获取全类名System.out.println(catClass.getName());//获取简单类名System.out.println(catClass.getSimpleName());//获取本类及父类中的所有public属性Field[] fields = catClass.getFields();for (Field field : fields) { //不使用getNam()的输出:public int reflection.Cat.size //使用getNam()的输出:size System.out.println(field.getName());}//获取本类中所有的属性Field[] declaredFields = catClass.getDeclaredFields();for (Field declaredField : declaredFields) { System.out.println(declaredField.getName());}//获取本类及父类的public方法Method[] methods = catClass.getMethods();for (Method method : methods) { System.out.println(method.getName());}//获取本类中所有的方法Method[] declaredMethods = catClass.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {System.out.println(declaredMethod);}//获取本类所有的public构造器(并没有拿到父类的)Constructor>[] constructors = catClass.getConstructors();for (Constructor> constructor : constructors) { System.out.println(constructor.getName());}//获取本类所有的构造方法Constructor>[] declaredConstructors = catClass.getDeclaredConstructors();for (Constructor> declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor.getName());}//获取包信息System.out.println(catClass.getPackage());//以Class形式返回父类信息System.out.println(catClass.getSuperclass());//返回接口信息Class>[] interfaces = catClass.getInterfaces();for (Class> anInterface : interfaces) { System.out.println(anInterface.getName());}//返回注解信息Annotation[] annotations = catClass.getAnnotations();for (Annotation annotation : annotations) { System.out.println(annotation);}
String fullpath = "reflection.Cat"; Class<?> catClass = Class.forName(fullpath); //获取本类中所有的属性 /* *获取修饰符时:默认修饰符 0 public 1 private 2 protected 4 static 8 final 16 * 有多个修饰符时,数字相加 * */ Field[] declaredFields = catClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("属性名:"+declaredField.getName() +" 属性访问修饰符"+declaredField.getModifiers() +" 属性的类型"+declaredField.getType()); } //获取本类中所有的方法 Method[] declaredMethods = catClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("方法名:"+declaredMethod +" 方法访问修饰符:"+declaredMethod.getModifiers() +" 方法返回类型:"+declaredMethod.getReturnType()); Class>[] parameterTypes = declaredMethod.getParameterTypes(); for (Class> parameterType : parameterTypes) { System.out.println("参数类型"+parameterType.getName()); } } //获取本类所有的构造方法 Constructor>[] declaredConstructors = catClass.getDeclaredConstructors(); for (Constructor> declaredConstructor : declaredConstructors) { System.out.println("构造器名:"+declaredConstructor.getName() +" 构造器访问修饰符"+declaredConstructor.getModifiers()); Class>[] parameterTypes = declaredConstructor.getParameterTypes(); for (Class> parameterType : parameterTypes) { System.out.println(parameterType.getName()); } }