八种基本数据类型:byte(1),short(2),int(4),long(8),float(4),double(8),boolean(1),char(2)
基本数据类型默认值
数据类型 默认值 byte 0 short 0 int 0 long 0L float 0.0F double 0.0 boolean false char \u0000 引用数据类型 null 八种数据类型大小排序:byte<short(32767)<int(2147483647)<long<float<double
char(虽然char和short占用的字节数相同,但是char可以取到更大的正整数)
==除了boolean类型外其他其中都可以进行类型转换==
小容量--->大容量 ==自动类型转换==
大容量--->小容量 ==强制类型转换== 需要加入强制类型转换符,虽然编译可以通过,但是运行可能会损失精度
注:
当整数字面值没有超过byte short char的取值范围,可以直接赋值
long g=10; byte h=(byte)(int)g/3;//虽然表达式右边的数值没有超过byte的范围,但是编译阶段过不去,因为编译阶段只看语法不做运 //算,如果这里直接赋值一个3是可以编译通过的
byte short char混合运算的时候,都先转换成int之后再进行运算
多种数据类型混合运算,先转成大容量数据类型再进行计算
==在一个域中变量名不能重名==
double dd=10/3; //10和3都是int,取整得3,之后自动类型转换成double得到3.0 dd=10.0/3; //3先转换成double,之后计算得到的就是double,结果为3.33333333
==0的ascii是48,a是97,A是65==
< = > 关系运算符的结果都是boolean类型
& 逻辑与 ! 逻辑非 |逻辑或 ^逻辑异或(==两边算子不一样就是true==)
&&短路与 ||短路或 ==逻辑运算符要求两边都是boolean类型,并且最终结果也是boolean类型==
==&&和& 的区别==:运算结果相同,只不过短路与存在短路现象
int x=10; int y=8; System.out.println(x<y & ++x<y);//x<y即是false,所以后边并没有执行的必要 System.out.println(x); //x=11,说明&后边的语句执行了 ==================================================================== int x=10; int y=8; System.out.println(x<y && ++x<y);//x<y即是false,所以后边并没有执行的必要 System.out.println(x); //x=10,说明&&后边的语句没有执行,直接短路了
==||和| 的区别==:运算结果相同,只不过短路或存在短路现象
= += *= /= %=
switch的语法结构
switch(int或String类型的字面值或者变量){ case int或String类型的字面值或者变量: java语句; break; //如果这里的break没有,就会存在case穿透现象,也就是下一个case内容一定会执行 case int或String类型的字面值或者变量: java语句; break; ... default: java语句; ... }
**注意:**byte short char可以放在==switch和case==后边,因为存在自动类型转换 ==java6之前只能用int,String都不能用,switch也支持枚举==
//case可以合并 int i=3; switch(i){ case 1:case 2:case 3:case 10: System.out.println("Test Code!") } //java输入 java.util.Scanner s=new java.util.Scanner(System.in); int num =s.nextInt();//next()是输入字符串 ======================= InputStreamRead is=new InputStreamRead(System.in); BufferedRead br=new BufferedRead(is);
swich的简单应用(简单计算器)
public class SimpleComputer { public static void main(String[] args) { java.util.Scanner scanner=new java.util.Scanner(System.in); System.out.println("欢迎使用简单计算器"); System.out.println("请输入你的第一个数字"); int a=scanner.nextInt(); System.out.println("请输入你的运算符"); String fuhao=scanner.next(); System.out.println("请输入你的第二个数字"); int b=scanner.nextInt(); switch (fuhao){ case "+": System.out.println(a+"+"+b+"="+(a+b)); break; case "-": System.out.println(a+"-"+b+"="+(a-b)); break; case "*": System.out.println(a+"*"+b+"="+(a*b)); break; case "/": System.out.println(a+"/"+b+"="+(a/b)); break; case "%": System.out.println(a+"%"+b+"="+(a%b)); break; } } }
==JVM== :栈 堆 方法区
栈内存主要存储==局部变量==
什么时候考虑使用方法重载?
功能相似
什么条件满足之后构成方法重载?
- 在一个类中
- 方法名相同
- 参数列表不同
- 数量不同
- 顺序不同
- 类型不同
//方法重载的print应用 Class Test{ public static void main(String[] args){ U.p(1); U.p(flase); U.p("1"); } } Class U{ public static void p(byte b){ System.out.println(b); } public static void p(short b){ System.out.println(b); } public static void p(int b){ System.out.println(b); } public static void p(long b){ System.out.println(b); } public static void p(float b){ System.out.println(b); } public static void p(double b){ System.out.println(b); } public static void p(boolean b){ System.out.println(b); } public static void p(char b){ System.out.println(b); } }
递归很耗费内存,可以不用的时候尽量不用,
当递归一直未结束(没有结束条件),会出现以下错误
- ==java.lang.StackOverflowError== 栈内存溢出错误,这是个错误不是异常,错误的结果就是JVM停止工作
//java递归算1~N的和 public class Test{ public static void main(String[] args){ sum(N); } public static int sum(int N){ if(N==1) return 1 return N+sum(N-1); } } //java递归算1~N的阶乘 public class Test{ public static void main(String[] args){ sum(N); } public static int mut(int N){ if(N==1) return 1 return N*mut(N-1); } }
对象是new出来的,存储在堆内存中
对象的地址,给的那个变量称为引用,引用可以是局部变量,也可以是成员变量,存储在栈内存中
==空引用在读取对象的时候会出现空指针异常==
this是一个关键字,是一个引用,this变量保存的内存地址是指向自身的
每个对象都有一个this
//this是一个引用 class Customer{ String name; public Customer(){} public void shopping(){ sout(this.name+"正在购物!!!"); } } public class Test{ psvm(){ String name="张三"; Customer a=new Customer(); a.name=name; a.shopping(); } }
==注意:==this不能出现在静态方法当中,因为静态方法不涉及引用,==即:实例变量只能在实例方法中使用(除非new对象)==
==带有static的方法不能直接调用实例方法或实例变量,因为实例方法和实例变量都需要对象==
this大部分情况下都能省略,什么时候不能省略呢?
//构造方法或者get set方法 public Student{ private num; private name; public Student(String name,int num){ num=num; //因为有就近原则,这里的num是局部变量,和实例变量没关系,所以这里要使用this name=name; } }
this除了可以用在实例方法中,还可以用在构造方法中 :(==this(实际参数列表)==)
==注意==:this只能出现在构造方法的第一行,并且只能写一行,如果出现在第一行,就会出现错误
//构造方法重复代码 class Date{ private int year; private int month; private int day; public Date(){ /* 所以以下代码可以改写成 this.year=1700; this.month=12; this.day=1; */ this(1700,12,1); } public Date(int year,int month,int day){ this.year=year; this.month=month; this.day=day; } }
复习this
- this只能出现在实例方法和构造方法中
- this的语法是 this. this()
- this不能出现在静态方法中
- this. 大部分情况下是可以省略的
- this. 在区分局部变量和实例变量的时候不能省略
- this() 只能出现在构造方法的第一行,通过当前构造方法去调用"本类"中的其他构造方法,目的是 代码复用
super:==super不是一个引用,super也不保存内存地址,super也不指向任何对象,super只是代表当前对象内部的那一块父类型的特征== 所以单独使用this可以,但是直接使用super会出问题,super后边必须跟.或者()
- super只能出现在实例方法和构造方法中
- super的语法是 super. super()
- super不能出现在静态方法中
- super. 大部分情况下是可以省略的
- super. 子类中有和父类同名的属性,并且希望在子类中使用父类的属性,那么就是要super不能省略
- super() 只能出现在构造方法的第一行,通过当前构造方法去调用"父类"中的其他构造方法,目的是 代码复用
- super后边不仅可以跟属性也可以跟方法
public class Test{ psvm(){ new B(); } } class A{ public A(){ sout("调用A类的无参数构造方法"); } } class B{ public B(){ super(); //这一行是隐形的 所以子类有的构造方法,父类必须有相对应参数的构造方法与之对应,不然super()方法会报错 sout("调用B类的无参数构造方法"); } } //结果是 调用A类的无参数构造方法 调用B类的无参数构造方法
super() 对应的现实中就是,要有儿子必须先要有老子
==关于this和super题目==
public class Test{ public static void main(String[] args){ new C(); } } class A{ public A(){ System.out.println("A的无参数构造方法");1 } } class B extends A{ public B(){ System.out.println("B的无参数构造方法");2 } public B(String name){ System.out.println("B的有参数构造方法(String)");3 } } class C extends B{ public C(){ this(name); System.out.println("C的无参数构造方法");4 } public C(String name){ this(name,age); System.out.println("C的有参数构造方法(String)");5 } public C(String name,int age){ super(name); System.out.println("C的有参数构造方法(String ,age)");6 } } //输出顺序是 13654
父类中的private属性 只能在本类中使用
比如子类的构造方法需要访问父类的private属性的时候,就需要使用super()调用父类的有参数构造方法
public class Test{ public static void main(String[] args){ Vip a=new Vip("张三"); a.shopping(); System.out.println(a.name); } } class Customer{ public String name; public Customer(){} public Customer(String name){ super(); this.name=name; } } class Vip extends Customer{ //private String name; //如果加上这一行代码 结果this 是null super是张三 没有this 和super 默认是this public Vip(){} public Vip(String name){ super(name); } public void shopping(){ System.out.println(this.name+"正在购物"); System.out.println(super.name+"正在购物"); System.out.println(name+"正在购物"); } }
static{ java语句; java语句; .... }
类加载的时候执行,并且只执行一次,作用:给程序员一个时机--类加载时机
==其他代码块==
基本代码块:
在main方法中 直接{ java语句 }使用 按照上下顺序进行运行
构造代码块:
在对应的class中, 直接{ java语句 }使用 按顺序运行,之后才会调用构造方法
继承的缺点:耦合度太高
子类继承父类,相当于把父类的代码复制了一份,只是不在子类中显示罢了
实际开发中什么时候用extends呢?
- 猫是一个动物
- 信用卡账户是一个银行账户
- 白菜是一种蔬菜
以上这种 是 或者 is 都是可以进行继承的突破口
==面向对象编程:OOA(面向对象分析),OOD(面向对象设计),OOP(面向对象编程)==
==封装的作用:==
java源码位置:==jdk/lib/src/java.base/java/==
object类中的方法:
toString()
直接对未重写toString方法的对象执行.toString得到的结果 xxxx@yyyy 这里的yyyy对应的是该对象所在内存地址经过“哈希算法”得到的十六进制结果,xxxx是对象的类型
如果直接sout对应的引用,那么java会默认调用这个引用的toString方法
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }//源代码
equals()==之后再讲==
public boolean equals(Object obj) { return (this == obj); }//equals方法的源代码 //重写equals方法 public boolean equals(Object obj){ if(obj==null) return false; if(obj==this) return true; if(!(obj instanceof MyTime)) return false; MyTime m=(MyTime)obj; if(this.year==m.year&&this.month==m.month&&this.day==m.day) return true; }
java中所有的==基本数据类型==比较是否相等使用==
java中所有的==引用数据类型==比较是否相等使用equals方法
clone()
hashcode
finalize()
//Object中的源码 protected void finalize() throws Throwable{} //这个方法不需要程序员自己来调用,JVM的垃圾回收器负责调用
==finalize方法的执行时机==:当一个对象即将被垃圾回收器(GC)回收的时候,垃圾回收器负责调用finalized方法
==finalize方法的作用==:对象销毁的时机叫做垃圾销毁时机,如果希望在对象销毁时机执行一段代码,就需要讲代码写入finalize方法中
==静态代码块是类加载时机==
==finalize是垃圾回收时机==
当给对应的对象赋值null的时候就相当于给它回收了
建议启动垃圾回收器 System.gc();
==注意==:在源码中,如果一个方法是以“;”结尾的,并且修饰符列表有“native”关键字,那么其底层是调用了c++写得dll(动态链接库)文件
String重写toString和equals方法
两个字符串进行比较直接使用String重写的==equals==方法即可进行比较
String类的==toString==方法直接输出对应的字符串
什么时候使用方法覆盖呢?
子类继承父类之后,当继承过来的方法无法满足子类的业务需求的时候,需要考虑重写
方法重写=方法覆盖
override、overwrite != overload(方法重载)
当子类对父类的方法进行重写的时候,父类的方法仍然在子类当中,不过继承过来的方法已经被标记了,所以JVM只认识我们程序员自己写得重写之后的方法,不会调用已经标记的继承过来的父类的方法
==方法覆盖必须:方法名相同,参数列表相同,相同的返回值类型==
方法覆盖注意事项:
- ==继承中,对于方法的覆盖,访问权限只能越来越高,不能越来越低 并且 重写之后的方法不能比之前的方法抛出更多的异常==
- ==方法覆盖只针对方法,和属无关==
- ==私有方法无法覆盖==
- ==构造方法不能继承,所以也不能覆盖==
- ==静态方法覆盖没有意义==
向上转型(子转父)和向下转型(父转子)的前提都是 ==要有继承关系== ==编译过程不会new对象==
//多态 Animal a=new Cat(); a.move(); //编译阶段:只知道a是一个Animal,并且去Animal.class中找到了move方法,所以编译通过(绑定父类方法) //运行阶段:a指向自己的子类对象,运行子类对象的move方法(动态绑定子类方法)
什么时候要向下转型 当你要访问的是子类中特有的方法的时候需要向下转型
为什么要使用instanceof关键词(运行阶段动态判断) 因为向下转型有风险 比如如下代码
Animal a=new Bird(); Cat b=(Cat) a; b.catchMouse(); //运行错误
instanceof的结果是boolean类型 用法 引用 instanceof 对象
多态在开发中的作用是:
==降低程序的耦合度,提高扩展力==
final是java语言中的一个关键字
final标志最终的不可变的
final可以修饰变量、方法还有类
final修饰的方法无法被覆盖
final修饰的类无法被继承
final修饰的变量一旦赋值之后不能重新赋值(final修饰一个引用也是这个道理,因为引用中存储的是一个地址,一旦final修饰的引用指向了对应的对象,也就是地址已经写入到了该引用内存,那么地址不可再变)
final修饰的实例变量,系统不管赋值,要求程序员自己赋值 ==实例变量在new对象的时候赋值==
class User{ final double height=1.8; final double weight;//这一行不可行,系统不管赋值 public User(){ weight=0.8;//这样可以,虽然无参构造,或者无构造的时候,系统会自动赋值,但是这里是在系统赋值之前进行的手动赋值,所以编译可以通过 } }
==如果不想让某个类被继承,那么可以用final修饰符来修饰该类== String就是final修饰的不可继承
==子类中特有的方法,在使用多态的时候会报错,因为编译器只会看父类引用是否有对应名称的方法,没有就会直接报错,除非强制向下转型,或者不适用多态==
final不管能不能调用,final管的是方法变量和类是最终的,不变的
对于chinese这样的类,国家是一个固定值,不管加不加final,只要是实例变量,就会占用内存空间(在堆中)。
所以final一般修饰的实例变量都要加static ----->static final+变量名,==静态变量存储在方法区==,也就是常量。
==注意==:
- 常量的命名,建议全部大写, 每个单词之间使用下划线连接
- 常量和静态变量,都是存储在方法区,并且都是在类加载的时候初始化
- ==常量一般都是公开的,因为公开你也改不了,不用封装==
银行账户类是==抽象类==
信用卡类是==类==
下边对应的卡是==对象==
当然==抽象类仍然可以进一步抽象==
抽象类无法进行实例化,因为抽象类的具体---->类本身在生活中就是不存在的实体
抽象类属于==引用数据类型==
抽象类的语法
[修饰符列表] abstract class 类名{ 类体; }
final是为了防止继承
abstract是为了继承 所以final和abstract是==敌人== 所以final和abstact不能联合使用
抽象类不能实例化对象,但是有构造方法,这个构造方法是给子类创造对象使用的
没有实现的方法, 没有方法体的方法就是抽象方法
public abstract void doSome(); //特点1:没有方法体,以分号结尾 //特点2:前边的修饰符列表有abstract关键字
抽象类不一定有抽象方法,并且抽象类中也可以有非抽象方法,抽象方法必须出现在抽象类中
==非抽象类继承抽象类,必须将抽象类的抽象方法实现==
==面向抽象编程==
public class Test{ public static void mian(String[] args){ Animal a=new Bird();//这就是面向抽象编程 } } abstract class Animal{ } class Bird extends Animal{ }
==OCP原则==:开闭原则,是说一个实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化
接口是==完全抽象的==
接口支持多继承,一个接口可以继承多个接口,==接口中只包含两部分内容:1、常量 2、抽象方法==
- 接口中方法的修饰符列表可以省略:public abstract
- 接口中常量的修饰符列表也可以省略:public static final
接口中的所有元素都是公开的(public修饰的)
语法是:
[修饰符列表] interface 接口名{}
类和类之间叫做继承,类和接口之间叫做实现
==缺省的权限小于public==
一个类可以实现多个接口
==注意==:
class Test{ public static void main(String[] args){ M a=new E(); K b=(K)a;//接口和接口之间进行强制类型转换的时候,没有继承关系也可以强转 //类转成一个没有实现的接口,可以强转成改接口 //运行的时候可能会出现ClassCastException } } interface K{} interface M{} class E implements M {}
==向上转型或向下转型的前提是两个类之间有继承关系,否则在编译期间就会报错,但是这个结论不适用在接口方面==
一个类同时继承类和接口的时候,先extends之后再implements
面向接口编程,有了接口就是可插拔,可插拔也就是 低耦合 高扩展
==has a就要以属性的形式存在==
面向接口编程,可以降低程序的耦合度,提高程序的扩展力,符合OCP原则,接口的使用离不开多态(接口+多态才能解耦合)
//去餐厅吃饭的接口例子 public class Test{ public static void main(String[] args){ FoodMenu f=new AmericaCook(); Customer c=new Customer(f); c.order(); } } interface FoodMenu{ void xihongshichaojidan(); void yuxiangrousi(); } class ChineseCook implements FoodMenu{ public void xihongshichaojidan(){ System.out.println("中国厨师做西红柿超级蛋"); } public void yuxiangrousi(){ System.out.println("中国厨师做鱼香肉丝"); } } class AmericaCook implements FoodMenu{ public void xihongshichaojidan(){ System.out.println("美国厨师做西红柿超级蛋"); } public void yuxiangrousi(){ System.out.println("美国厨师做鱼香肉丝"); } } class Customer{ private FoodMenu foodmenu; public Customer(FoodMenu foodmenu){ this.foodmenu=foodmenu; } public void order(){ foodmenu.xihongshichaojidan(); foodmenu.yuxiangrousi(); } }
接口解耦合,解的是 ==调用者和实现者的耦合==
==接口和接口的区别==:
- 接口是完全抽象的 抽象类是半抽象的
- 接口没有构造方法 抽象类有构造方法
- 接口和接口之间支持多继承 类和类之间只能单继承
- 一个类可以同时实现多个接口 但是一个抽象类只能继承一个类
包名的命名规范: ==公司域名倒叙+项目名+模块名+功能名==
package需要出现在代码的第一行 import再package下边
如果代码第一行有包名 如何编译 可以直接讲对应的包全部建好 ==javac -d . 项目名.java==这里的.是当前目录的意思
java.lang包小的类都不需要导包
4个访问控制权限
- private 表示私有的,只能再本类中使用
- public 公开的,在任何位置都可以访问
- protected 只能在本类或者同包或者子类中访问
- 默认啥也不写 只能在本类,以及同包下访问
访问控制符 本类 同包 子类 任意位置 public 可以 可以 可以 可以 protected 可以 可以 可以 不行 默认 可以 可以 不行 不行 private 可以 不行 不行 不行 访问修饰符可以修饰什么?
- 属性 4个都可以
- 方法 4个都可以
- 类 只能public 和默认
- 接口 只能public和默认
Application Program Interface(应用程序编程接口)
整个JDK的类库就是javase的API
内部类的分类:
静态内部类:相当于静态变量
实例内部类:相当于实例变量
局部内部类:相当于局部变量
class Test{ static class Inner1{} //静态内部类 class Inner2{} //实例内部类 public void doSome(){ class Inner3{} //局部内部类 } }
匿名内部类是局部内部类的一种
//举例说明 class Test{ public static void main(String[] args){ MyMath m=new MyMath(); m.mySum(new Computer(){ public int sum(int a,int b){ return (a+b); } },10,20); } } interface Computer{ int sum(int a,int b); } class MyMath{ public void mySum(Computer c,int a,int b){ System.out.println( c.sum(a,b)); } }
为什么不建议使用匿名内部类?
==因为只能用一次,没有直接用类实现接口方便==
数组是存放在==堆内存==的
数组一旦创建,Java中规定,长度不可再变
length属性是数组自带的
数组的优缺点:
==优点==:
- 每一个元素的内存地址空间存储上都是连续的
- 每一个元素在空间中所占的内存都是一样大的
- 知道第一个元素的位置,知道每个元素的内存大小,可以直接通过数学计算得到对应的元素位置,所以一个100大小的数组和100w大小的数组检索的效率都是一样的
==缺点==:
- 由于地址是连续的,所以对数据进行增删的时候效率低,因为随机增删会涉及到后边元素统一前移或者后移。
- 数组不能存储大数据量,因为在内存中很难找到一块很大的地址连续内存空间
- ==数组的最后一个元素的增删,没有效率影响==
数组初始化
- 静态初始化 int[] a={1,2,3};
- 动态初始化 int[] a=new int[5];
- sout(new int[] {1,2,3})
==数组扩容== :数组扩容效率较低,涉及到数组拷贝的问题
int[][] a=new int[2] [2]
a[0] [0]的意思是第一个以为数组的第一个元素
sout(new int[ ] [ ] {{1,2,3},{2,3,4},{2,3,4}});
异常在java中是以类的形式存在,每一个异常类都可以创建异常对象
不管是错误还是异常都是可抛出的
==所有的Error,java程序只有一个结果就是终止程序的执行,推出JVM==
==所有的RuntimeException及其子类都属于运行时异常==
所有Exception的直接子类,都叫做==编译时异常==
==编译时异常的意思是在编译阶段发生的异常嘛?==
- 不是,编译时异常的意思是,在编译阶段程序员就要处理的异常,如果不处理编译器会报错
- 运行时异常在编写程序阶段,==可以选择处理,也可以选择不处理==
编译时异常和运行异常,都发生在运行阶段,编译阶段异常是不会发生的,因为异常是new出来的,只有程序运行阶段才可以new对象
==编译时异常和运行时异常的区别==
编译时异常(受检异常,受控异常)发生的概率比较高
==举例说明==:
看见外边下雨,防止出门淋雨感冒(异常),所以出门带了一把伞(感冒异常发生之前的处理方式)
对于这种概率较高的异常,需要运行之前进行预处理
运行时异常(未受检异常,非受控异常)发生的概率比较低
==举个例子==:
出门可能被飞机砸到(异常)
没必要对这种低概率异常进行预处理,不然会活得很累
java中异常处理的两种方式:
- 在方法声明的地方,使用throws关键字
- 使用try...catch语句对异常捕捉
如果一直throws给上级,直到main抛给了JVM之后,只有一个结果,就是JVM终止程序运行
class Test{ public static void main(String[] args){ dosome();//因为doSome方法有throws声明,所以在调用doSome方法的时候就要对异常进行处理,要么继续throws,也么try catch,catch的写法 catch(Exception e){e.printStackTrace();} } public static void doSome() throws ClassNotFoundException{ System.out.println("doSome"); } } //手动抛异常 if(要判断的异常){ throw 要抛出的异常对象 }
==代码出现异常,一个域内,出现代码之后的代码不会执行== ==catch的后续代码会执行== catch可以写多个,这样精确到一个个的时候,有利于程序的调试
catch中的异常可以通过 逻辑 | 进行统一
==如何选择throws和try catch呢?== 如果确定了调用者需要处理,就throws
String msg=exception.getMessage();//获取异常简单的表述信息
exception.printStackTrace();//打印异常追踪的堆栈信息 print异常信息有一个单独的线程负责
finally通常使用在什么情况?
try{ FileInputStream f=new FileInputStream("路径"); String s=null; s.toString();//这里会出现空指针异常 f.close();//关闭流 }catch(Excepption e){ e.printStackTrace(); } //以上代码如果不加finally的话,close就关闭不了,一直占用资源 //finally中的代码一定会执行
return不会对finally的运行产生影响 但是System.exit() 退出JVM语句,会影响finally的运行
==一道简单的finally的面试题==
public static int m(){ int i=100; try{ return i; }finlly{ i++; } }//上述代码的最终结果是100,因为只要return i出现在i的下边,就会遵循java更古不变的自上而下的规则 //以上代码 反编译之后的结果如下 public static int m(){ int i=100; int j=i; i++; return j; }
final
- final修饰的类无法继承
- final修饰的方法无法覆盖
- final修饰的变量无法重新赋值
finally
- 和try一起使用
- finally语句块中的代码必须执行
finalize
- 是一个Object类中的方法名
- 这个方法是由垃圾回收器GC负责调用的
/* 两步: 1、编写一个类继承Exception或者RuntimeException 2、提供两个构造方法,一个无参的,一个String的 */ public class MyException extends Exception{ //Exception对应编译时异常 RuntimeException对应运行时异常 public MyException(){ } public MyException(String s){ //这里的s就是 getMessage的结果 super(s); } }
集合存储的都是引用数据类型
集合在java.util.*中 ArrayList 底层是数组 LinkedList 底层是链表 TreeSet 底层是二叉树
接口Interable(==Interator方法==)<-----接口Collection<----接口Interator(==hasNext()、next()、remove()方法==) 两个都是接口
Collection有两个泛化(继承)接口,分别是List和Set:
==List==:存储的元素有序可重复,有下标,有序的意思是==存进和取出的顺序不变==,有序是因为有下标
ArrayList(初始化容量是10,扩容是1.5倍)
ArrayList有三种构造方法
- 无参的 2.传入int类型的集合初始容量 3.传入一个结合
底层是Object类型的数组,非线程安全的
如何变成线程安全的呢
List l=new ArrayList(); Collections.synchronizedList(l);//集合工具类
初始化容量是10(底层是:==先创建一个容量为0的数组,当添加第一个元素的时候,初始化结合容量为10==),如果add的元素如果超过了初始容量,那么集合就会扩容为之前的1.5倍,ArrayList优化,最好还是少进行扩容,因为数组扩容效率比较低
初始化容量可以使用int构造方法传初始化容量进去
数组优点:检索效率比较高 缺点:增删改查效率低 但是向数组末尾添加元素效率很高
LinkedList(没有初始化容量)
底层采用了==双向链表数据结构==
//单链表的简单实现代码 public class Test { public static void main(String[] args){ Link l=new Link(); l.add("aaa"); System.out.println(l.header.data+"?"+l.end.data+"size"+l.size); } } class Node { public Object data; public Node next; public Node(){} public Node(Object data,Node next){ this.data=data; this.next=next; } } class Link { int size=0; Node header=null; Node end=null; public void add(Object obj){ if(header==null){ header=new Node(obj,null); end=header; }else{ Node newNode=new Node(obj,null); end.next=newNode; end=newNode; } size++; } public void remove(Object obj){} public void modify(Object newObj){} public int find(Object obj){return 1;} } //注意************ //LinkedList也有下标,List都有下标,但是ArrayList的检索效率高并不是仅仅因为有下标 //是因为ArrayList的底层是数组,而LinkedList的底层是有下标的链表,导致它的检索或者查找元素的效率比较低,只能从头节点开始一个一个遍历 链表的优缺点: 优点:链表不需要连续存储空间 因此随机增删效率很高,所以在遇到随机增删集合中元素的业务比较多时,建议使用 LinkedList 缺点:不能像数组那样通过数学表达式的方式得到对应元素的内存地址,每次都需要从头节点 进行一个一个检索,效率较低 ArrayList把检索发挥到了极致 LinkedList把随机增删发挥到了极致 不过平时开发都是往末尾添加元素,所以ArrayList用的是比LinkedList多的
Vector(初始化容量是10,扩容是2倍)
- 底层采用数组数据结构,相对于ArrayList,Vector是线程安全的(sychronized,虽然线程安全,但是效率较低,因为现在线程安全有别的方案,所以Vector用的少了)
==Set==:无序不可重复,没有下标,所以无序(不可重复是==如果重复只会存在一个结果==)
HashSet
- new HashSet实际上是new HashMap,所以HashSet存储元素也是存到了HashMap当中了,所以其底层是哈希表数据结构
TreeSet
TreeSet的父接口是SortedSet,再往上才是Set接口,new TreeSet底层实际上是new TreeMap,所以其底层是二叉树数据结构,放在TreeSet里的==数据可以自动按照大小排序==
对于自定义的类型来说,TreeSet可以排序吗 ? 不能,因为没有指定对应的比较规则
解决方式
==需要实现Comparable接口才可以==(Comparable是lang下的)
class WuGui implements Comparable{ int age; public WuGui(){} public WuGui(int age){this.age=age} public int compareTo(WuGui o){ return this.age-o.age; } }
构造方法传入比较器(Comparator是util下的)
class MyComparator implements Comparator<WuGui>{ public int compare(Wugui o1,WuGui o2){ return o1.age-o2.age; } } class Test{ public static void main(String[] args){ MyComparator m=new MyComparator(); Set<WuGui> set=new TreeSet(m); } }
使用匿名内部类
Set<Customer> m=new TreeSet<>(new Comparator<Customer>() { @Override public int compare(Customer o1, Customer o2) { return o1.getAge()-o2.getAge(); } });
==对于以上三种方式==:要选哪种方式,如果比较规则很少改变,建议使用Comparable接口
如果比较规则有多个,并且多个比较规则之间频繁切换,建议使用Comparator接口
TreeSet 使用的遍历方式是:中序遍历
==Collection中可以存储什么东西?==集合中不能直接存储基本数据类型,也不能存java对象,只能存储java对象的内存地址
==Collection中的方法:==
boolean add(E e)
int size() ==获取当前集合中的元素个数, 不是获取集合的容量==
void clear()
boolean contains(Object o)
public class Test{ public static void main(String[] args){ Collection c=new ArrayList(); String s1=new String("abc"); c.add(s1); String s1=new String("def"); c.add(s2); String x=new String("abc"); sout(c.contains(x));//contains底层调用的是equals方法,对比的是String,所以结果是true } } //重写equals方法 public boolean equals(Object o){ if(o==null || !(o.instanceof(User))) return false; if(o==this) return true; User u=(User) o; return (o.name.equals(this.name)); } //结论:存放在集合中的类型,一定要重写equals方法
boolean remove(Object o)
Collection c=new ArrayList(); String s1="abc"; c.add(s1); String s2="abc"; c.remove(s2); sout(c.size());// 说明remove底层调用了equals方法 Collection c=new HashSet(); c.add("aa"); c.add("abb"); c.add("ab");//存进去什么类型,取出来还是什么类型,只不过输出的时候会调用toString方法 Iterator i=c.iterator(); while(i.hasNext()){ sout(i.next()); c.remove(i.next());//会出现异常,因为集合的内容发生了变化 所以这里可以使用迭代器的remove方法 i.remove(); 即删除迭代器当前所指的对象 }
boolean isEmpty()
Object[] toArray() 将集合转换成数组
Ierator iterator()
Collection c=new HashSet(); c.add("aa"); c.add("abb"); c.add("ab");//存进去什么类型,取出来还是什么类型,只不过输出的时候会调用toString方法 Iterator i=c.iterator(); while(i.hasNext()){ sout(i.next()); }
迭代器对象的两个方法
boolean hasNext()
Object next()
List中的方法,在Collection的基础上:
- void add(int index,E element) 这样添加效率太低
- E get(int index) 因为有下标,所以List集合有他自己的遍历方式
- int indexOf()
- int lastIndexOf()
- E remove(int index)
- E set(int index,E element)
==Key和value里都存着java对象的内存地址==,所有Map的key都是无需不可重复的,Map集合的ket和Set集合存储元素特点相同
==Map常用方法==
void clear()
boolean containsKey(Object key) contains方法底层都是equals方法
boolean containsValue(Object value)
V get(Object key) 通过key获取value
Set keySet() 获取所有的Key
V put(K key,V value) 添加键值对
V remove(Object key)
int size()
Collection values() 获取Map集合中所有的value,返回一个Collection
Set<map.Entry<K,V>>entrySet() 将Map集合转换成Set集合
key value =============== 1 zhangsan 2 lisi 上述Map通过entrySet之后,得到的结果如下: 1=zhangsan 2=lisi
import java.util.*; public class Test { //遍历Map集合 public static void main(String[] args){ Map<Integer ,String > map=new HashMap<>(); map.put(1,"zhangsan"); map.put(2,"wangwu"); //迭代器 Set<Integer> set=map.keySet(); Iterator<Integer> i=set.iterator(); while(i.hasNext()){ //Integer key=i.next(); //String value=map.get(key); //System.out.println(key+"="+value); System.out.println(i.next()+"="+ map.get(i.next()));//注意 调用一次next就会迭代一次 } //foreach for(Integer ii:set){ System.out.println(ii+"="+map.get(ii)); } //Set<Map.Entry<K,V>> entrySet() Set<Map.Entry<Integer,String>> set2=map.entrySet(); for(Map.Entry<Integer,String> s:set2){ System.out.println(s.getKey()+"="+s.getValue()); }//这种foreach适合大数据量使用,因为是直接通过Node的属性值进行获取的 } }
Map<----HashMap(非线程安全的,类) Map<----HashTable(线程安全的,过时了,类)<--Properties(类,存储在Properties的key和value都必须是String类型,不能是其他类型,被称为==属性类 ==) 两个底层都是哈希表
HashMap允许key和value值都可以为null(HashTable的key和value都不能为空)
HashMap扩容是2的倍数
Map map=new HashMap(); Integer i=map.put(null,100); sout(map.size());//1
==数组==是查询遍历效率高 ==链表==是随机增删改效率高 ==哈希表==是结合两个的优点
哈希表:底层是一维数组,这个数组的每个元素是一个单向链表
==map.put(k,v)的实现原理:==
- 先将kv封装到Node对象中
- 底层会调用hashMap()得到hash
- 通过哈希函数/哈希算法,将hash值转换成数组下标,下标位置如果没有任何元素,就把Node添加到或者位置,如果下标对应的位置有链表,拿着k和链表上每个节点的k进行equals,如果所有的equals方法返回都是false,那么就将新节点添加到链表的末尾,如果其中一个equals返回true,会把新的节点的value替换上
==map.get(key)的实现原理:==
先调用k的hashCode方法得到哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置,如果这个位置什么都没有,返回null。如果有一个单向链表,那么就一一equals k值,有的话返回对应的value,没有就返回null
==为了保证hashMap的key或hashSet的==无序不可重复==,对应的元素对象必须重写hashCode和equals方法==
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成一个纯单向链表,这种情况成为==散列分布不均匀==
==散列分布==需要重写hashCode的时候有一定的技巧
==重点==:HashMap集合初始化容量必须是2的倍数,因为可以达到散列均匀,提高存取效率
如果一个类的equals方法重写了,那么hashCode方法必须重写
如果哈希表的单个链表的元素超过8个,那么单向链表会编程红黑树结构,如果数量小于6,会将红黑树改成单向链表,为了提高检索效率
是一个Map集合,继承Hashtable,它的key和value都是String类型,属于==属性类对象==
Properties两个方法:
- setProperty
- getProperty
Map<--SortedMap(接口)<--TreeMap(类,数据结构是二叉树) SortedMap集合的key会自动按照大小顺序排序
没用泛型的话,迭代器拿出来的就是Object类型,代码不好写
List<Animal> myList=new ArrayList<Animal>();
==自动类型推断机制(又成为钻石表达式)==
//jdk8之后 new对象的时候<>中的类型可以不写,系统会自动检测出来 List<Animal> myList=new ArrayList<Animal>();
自定义泛型
public class Test<E(名字任意)>{} public E doSome(){} //java自带的E是Element T是type
缺点:没有下标
List<Integer> l=new ArrayList<>(); l.add(1); for(Integer i:l){ sout(i); }
Read Input InputStream --->读 读入 输入流 ==硬盘--->内存==
==IO流分类==:
- 按照流的方式分:输入 和 输出流
- 按照读取数据的方式不同: 字节流(什么类型的文件都可以读取) 和 字符流(主要为了读取普通文本文件)
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
四大家族的首领都是抽象类,所有的流都==实现了closeable接口==,所有流都是可关闭的,所有的==输出流都实现了Flushable==,所有输出流都是可刷新的,所以***==要养成好习惯,所有的流都要关闭,所有的输出流输出之后都要刷新==***
java.io包中需要掌握的流有16个
==文件专属==
- java.io.FileInputStream 构造方法有==append参数==,为true,说明文件内容可以追加
- ==对应方法==
- int read()
- int read(byte[] b)
- int available() 返回流当中剩余没有读到的字节数量
- long skip(long n) 跳过几个字节不读
- java.io.FileOutputStream
- ==对应方法==
- int write()
- int write(byte[] b)
- int write(byte[] b,int off,int len)
- 构造 FileOutputStream(String 文件名,boolean append) append如果为True,那么直接在文件后边追加,不会从头开始替换
- 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.DataInputStream
- java.io.DataOutputStream
==标准流==
- java.io.PrintWriter
- java.io.PrintReader
==对象流==
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
class Test { public static void main(String[] args) { FileInputStream f = null; try { f = new FileInputStream("Comparable/src/com/wzx/io/tempFile"); int readData = 0; while ((readData = f.read()) != -1) { System.out.println(readData); } } catch (FileNotFoundException fileNotFoundException) { fileNotFoundException.printStackTrace(); } catch (IOException ioException) { ioException.printStackTrace(); } finally { if (f != null) { try { f.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
IDEA默认的当前路径是哪里? 工程Porject的根就是默认当前路径
- read() 返回值是读取的数据
- read(byte[] b) 返回值是读取的字节数
class Test { public static void main(String[] args) { FileInputStream f = null; try { f = new FileInputStream("Comparable/src/com/wzx/io/tempFile"); int readCount = 0; byte[] b = new byte[4]; while ((readCount = f.read(b)) != -1) { System.out.println(new String(b, 0, readCount)); } } catch (FileNotFoundException fileNotFoundException) { fileNotFoundException.printStackTrace(); } catch (IOException ioException) { ioException.printStackTrace(); } finally { if (f != null) { try { f.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
available() 剩余字节个数
try { f = new FileInputStream("Comparable/src/com/wzx/io/tempFile"); byte[] b = new byte[f.available]; int readCount =f.read(b); System.out.println(new String(b));
skip() 跳过几个字节
public class Copy { public static void main(String[] args) { FileInputStream fi=null; FileOutputStream fo=null; try { fi=new FileInputStream("myfile"); fo=new FileOutputStream("Comparable/src/com/wzx/io/myfile"); byte[] b=new byte[1024*1024]; int readCount=0; while((readCount=fi.read(b))!=-1){ fo.write(b,0,readCount); } fo.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fi!=null){ try { fi.close(); } catch (IOException e) { e.printStackTrace(); } } if(fo!=null){ try { fo.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileReader reader=null; try{ reader=new FileReader("tempfile"); char[] c=new char[4]; int readCount=0; while((readCount=reader.read(c))!=-1){ sout(String(c,0,readCount)); } }catch{ } //或者 try{ reader=new FileReader("tempfile"); char[] c=new char[4]; reader.read(c); for(char cc:c){ sout(cc); } }
FileWriter 的write方法,可以直接传入字符串String类型
带有缓冲区的字符输入流,使用该流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。
==注意:==当一个流的构造方法中需要一个流的时候,这个被传进来的节点流称为节点流,外部负责包装的流称为包装流(处理流),对于包装类来说,关闭包装流,节点流就会自动关闭
FileReader reader=new FileReader("文件名"); BufferedReader br=new BufferedReader(reader); //String firstLine=br.readLine(); String s=null; while((s=br.readLine())!=null){ sout(s); } br.close();
FileInputStream in=new FileIputStream(); BufferedReader br=new BufferedReader(new FileInputStreamReader(in)); //合并 BufferedReader br=new BufferedReader(new FileInputStreamReader(new FileInputStream));
DataOutputStream dos=new DataOutputStream(new FileOutputStream("fileName")); byte b=100; //写到data文件中的不仅仅是具体的数据,还包括数据的类型都一并写入到了文件中 short s=200; //所以直接打开文件会出现乱码,所以这时候就需要DataInputStream来读取 int i=300; long l=400L; float f=4.4F; double d=3.14; boolean bo=true; char c='a'; dos.writerByte(b); //DataInputStream dis 的方法 dis.readByte().... dos.writerInt(i); ...... dos.writerChar(c); dos.flush();//刷新 dos.close();//关闭最外层
java.io.PrintStream 标准字节流输出 ==不需要关闭,默认输出到控制台==
PrintStream ps=System.out; ps.println("aaaa"); //标准输出流不需要 flush() 和 手动close()
标准输出流怎么指向别处,==不指向控制台==
PrintStream pr=new PrintStream(new FileOutputStream("fileName")); System.setOut(pr); System.out.println("aaa");//这里的输出方向就是file文件
//记录日志的文件 import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.Date; public class LoggerTest { public static void main(String[] args) { Logger.log("项目开始了"); } } class Logger{ public static void log(String msg){ try { PrintStream out=new PrintStream(new FileOutputStream("log.txt",true)); System.setOut(out); Date date=new Date(); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); String time=sdf.format(date); System.out.println(msg+time); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
File是一个文件路径的抽象表示形式
==File和四大流没有关系==
==File的常用方法==:
- 构造 File(String 路径)
- boolean exists() 判断是否存在
- creatNewFile() 以文件的形式创建出来
- mkdir() 以路径的形式创建出来
- mkdirs() 多重目录
- String getParent() 获取父路径
- File getParentFile() 获取父File
- getAbsolutePath() 获取绝对路径
- getName() 获取文件名
- isFile() 判断是否为一个文件
- isDirectory() 判断是否为一个路径
- long lastModified() 获取文件最后一次修改时间 返回值是毫秒,从1970年开始
- long length() 获取文件的大小
- File [ ] ==listFile()== 获取当前目录下的所有子文件
==将内存的Java对象,存储到硬盘文件中---->序列化==
序列化:ObjectOutputStream 进行拆分对象
//写一个Student类,对Student对象进行序列化 Student stu=new Student("zhangsan"); ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("studentFile")); //序列化 oos.writeObject(stu); oos.flush(); oos.close();
==没有内容的接口是标志性接口(起到标识的作用,Java虚拟机看该类实现了该接口,就会特殊对待,JVM看到标志性接口,就会给该类自动生成一个序列化版本号)==:Serializable接口就是标志性接口
==怎么一次序列化多个对象? 将对象放在集合中,序列化集合即可==注意:放进集合中的对象都要实现Serializable接口,如果不用集合,按顺序序列化,那么在第二个对象序列化的时候会报错
要实现对象序列化,必须将对应的类实现Serializable接口
//如果不想序列化某个实例变量的时候 private int a;=====> private transient int a; //虽然设置成游离的,但反序列化会得到对应变量的默认参数
反序列化:ObjectInputStream 进行组装对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("studentFile")); ois.readObject(); ois.close();
==序列化版本号==
10年前,写了一个Student类,并对其对象进行序列化。10年后,Student类序列化版本号改变了,如果直接对硬盘中的对象进行反序列化,会报错:java.io.InvalidClassException
==java中如何区分不同的类==
- 首先通过类名区分,类名不同,肯定不是一个类
- 如果类名一样,就要对比序列化版本号,版本号不同,不是一个类
==自动生成序列化版本号的缺陷:==
- 一旦代码确定之后,不能修改,修改之后会重新编译,这样序列化版本号就变了,JVM会认为这是一个全新的类
- 最终结论:凡是一个类实现了Serializable接口,建议给该类手动添加一个固定不变的序列化版本号
- 手动:private static final long serialVersionUID=1L
==IDEA自动生成序列化版本号:setting-Editor-Inspections-搜索serializable class without 'serialVersionUID '==
file文件内容:
username=admin
password=123
//读取file文件中的内容并存储到properties对象中 FileInputStream in=new FileInputStream("file"); Properties pro=new Properties(); //调用properties的load(Reader reader/InputStream in)将Map中的数据加载到Map集合当中 pro.load(in); //文件中=的左边是在properties的key,=右边是properties的value String username=pro.getProperty("username"); in.close();
IO和properties的设计理念的应用:经常需要修改的数据,可以单独写在一个文件里,需要使用的时候用程序动态调用即可。
我们把有这种机制的文件称为==配置文件==,并且如果文件中都是以key=value的形式出现的时候,我们称为属性配置文件,==属性配置文件中的注释用:#==
java规范:属性配置文件命名以properties结尾,但是不是必须的
什么是进程?什么是线程?
==一个软件就是进程==,线程是进程中的一个执行场景/执行单元
在运行java程序的时候,目前至少有两个线程在并发,JVM是进程,进程先调用main方法线程,再调用垃圾回收线程。
==注意== 线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
多线程的目的:为了提高程序的处理效率
==方法1==、编写一个类,直接继承java.lang.Thread,重写run方法
public class Test{ public static void main(String[] args){ MyThread thread=new MyThread(); thread.start(); } } class MyThread{ public void run(){ sout("子线程"); } }
上边代码中的start的作用是,在JVM中开辟一个新的栈空间 ,start执行完成,这个代码语句使命就结束了,启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底
<img alt="image-20220124204819694">
***==方法2、==***编写一个类,实现java.lang.Runnable接口,实现run方法
public class Test{ public static void main(String[] args){ MyThread thread=new MyThread(new MyRunnable); t.start(); } } class MyRunnable implements Runnable{//该类不是线程,只是一个可运行的类 public void run(){ sout("子线程"); } }
我们一般使用第二种,面向接口,因为还可以进一步继承类==以上代码还可以通过匿名内部类进行实现==
为什么线程的运行会一会前一会后? 因为声明周期中的==就绪态== ==运行态==
线程生命周期图
<img alt="image-20220124224440314">
设置线程的名称 thread.setName("aaa")
获取线程的名称 thread.getName(); 如果不设置名称系统获取名称会得到 "Thread-线程的次序"
获取当前线程对象 static Thread currentThread()
让当前线程进入休眠,进入“阻塞状态”,放弃占有的CPU时间片 static void sleep(long millis),该方法可以实现一定的效果==即,间隔特定的时间,去执行一段特定的代码,每隔多久执行一次==
//关于sleep的一个面试题 public static void main(String[] args){ Thread t=new Thread3(); t.setName("aa"); t.start(); t.sleep(1000*5);//这行代码是主线程休眠,因为sleep方法是静态的和对象无关,最终都是Thread.sleep() } class Thread3 extends Thread{ public void run(){ } }
如果sleep时间久了,怎么叫醒一个正在睡眠的线程,终端线程的睡眠,而不是终端线程的执行
实例方法==t.interrupt() 终止线程的睡眠==
t.
stop() 已过时不建议使用 强行终止线程的执行 ==缺点==:数据丢失
那么怎么合理终止一个线程呢? 答:打==布尔标记== 如下代码
class Test{ MyRunnable m=new MyRunnable(); Thread r=new Thread(m); r.setName("a"); r.start(); r.run=false;//终止线程执行 } class MyRunnable implements Runnanle{ boolean run=true; public void run(){ if(run){ }else{ //在return之前可以进行数据保存,从而防止stop引起的数据丢失 return; } } }
抢占式调度模型
- java就是使用这种模型,谁的优先级高,谁抢到cpu时间片的概率越高
均分式调度模型
==线程调度方法==
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程的优先级
最低的优先级是1,默认优先级是5,最高优先级是10
static void yield() 让位方法,暂停当前正在执行的线程对象,并执行其他线程,yield不是阻塞方法,yield的执行,会让当前线程从运行态到就绪态
void join() 合并线程
//举例 class Test{ MyThread t=new MyThread(); t.join();//使得主线程进入阻塞,t线程执行,直到t线程结束,主线程才可以运行 } class MyThread extends Thread{ }
==什么时候存在多线程问题呢?==
多线程并发
有共享数据
共享数据有修改的行为
public class BankTest { public static void main(String[] args) throws InterruptedException { Account act=new Account(10000); Thread t1=new Thread(new BankRunnable(act)); Thread t2=new Thread(new BankRunnable(act)); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } } class BankRunnable implements Runnable{ Account act; public BankRunnable(Account act) { this.act=act; } @Override public void run() { int balanceBefore=act.getBalance(); act.withDraw(5000); System.out.println(Thread.currentThread().getName()+"run从"+balanceBefore+"取出"+5000+"元,还有余额:"+(act.getBalance())+"元"); } } class Account{ private int balance; public Account() { } public Account(int balance) { this.balance = balance; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } public void withDraw(int num){ int balanceBefore=this.getBalance(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(getBalance()-num); } } /* t2从10000取出5000元,还有余额:5000元 t1从10000取出5000元,还有余额:5000元 */
==怎么解决线程安全问题呢?==
线程排队执行(不能并发),这种机制称为==线程同步机制==,同步机制会降低效率
通过同步机制来解决以上代码的安全问题:
//只需要修改会出现错误的操作代码,因为取钱的动作不能异步,所以要对取钱这个动作采用同步机制 public void withDraw(int num){ synchronized{//synchronized括号中的数据必须是多线程共享的数据,这里的代码共享对象是账户对象也就是this int balanceBefore=this.getBalance(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setBalance(getBalance()-num); } } /* t1run从10000取出5000元,还有余额:5000元 t2run从5000取出5000元,还有余额:0元 */ synchronized(){}称为同步代码块
怎么解释synchronized呢?
java语言中,每个对象都有一把==锁==,是一个标志。假设t1先执行,遇到了synchronized,就会锁定共享对象的锁,之后执行同步代码块中的代码,直到同步代码块中的代码执行完毕。如果在t1锁定对象锁之后,t2也遇到了synchronized,不要怕,锁已经被占有了,要等t1执行完同步代码块的代码再释放锁之后,t2才会执行。 ==()中放的共享对象一定要是多个需要排队的线程的共享对象==
对象是共享的,那么对象的实例对象也是共享的,只要是==对象==,就可以占有
如果是synchronized("abc") 那么所有线程都会同步
java中的三大变量:局部(栈中) 静态(方法区) 实例(堆 ) ==局部变量永远都不存在线程安全问题==,因为局部变量在栈内,一个线程一个栈,而方法区和堆只有一个,都是多线程共享的,所以可能存在线程安全问题
==编程模型==
- 异步编程模型
- 多线程并发,谁也不等谁
- 同步编程模型
- 排队执行
不仅可以直接在取钱方法体内使用同步机制,也可以直接在调用取钱方法的时候使用同步机制,如:sychronized(act){act.withDraw(5000)} ==这样扩大了同步范围,但是代码执行效率降低了==
synchronized修饰实例变量,public synchronized void withDraw 是将==this锁住了==
缺点:
- 这种方式不灵活 一直锁住this
- 这种方式会无缘无故扩大同步范围,从而降低代码执行效率
优点:
- 代码少了,所以共享对象是this,并且需要同步的代码块是整个方法体,那么建议实例方法使用synchronized
如果不存在线程安全问题,局部变量使用StringBuilder,因为StringBuilder不需要做同步处理,效率较高
- 同步代码块
- 实例方法使用synchronized
- 在静态方法上使用synchronized,表示找==类锁==,类锁只有一把,和对象的个数没有关系(第三种是为了保证==静态变量的安全==)
synchronized面试题
public class Test { public static void main (String[] args) throws Exception{ Myclass m=new Myclass(); MyThread t1=new MyThread(m); MyThread t2=new MyThread(m); t1.setName("t1"); t2.setName("t2"); t1.start(); try{Thread.sleep(1000); } catch (Exception e){} t2.start(); } } class MyThread extends Thread{ Myclass m=new Myclass(); public MyThread(Myclass m){ this.m=m; } public void run() { if(Thread.currentThread().getName().equals("t1")){ m.doSome(); } if(Thread.currentThread().getName().equals("t2")){ m.doOther(); } } } class Myclass { public synchronized void doSome() { System.out.println("doSome开始"); try{Thread.sleep(1000*10);} catch (Exception e){} System.out.println("doSome结束"); } public void doOther(){ System.out.println("doOther开始"); System.out.println("doOther结束"); } } //t1执行doSome并不影响t2执行doOther,因为doOther方法并不涉及到线程安全问题 //那么如果在doOther方法上加synchronized,那么就需要等待t1线程调用完doSome //如果创建两个Myclass对象分别给t1和t2,那么两者之间也需要等待
==类锁面试题,根据上边代码修改得到==
public class Test { public static void main (String[] args) throws Exception{ Myclass m=new Myclass(); MyThread t1=new MyThread(m); MyThread t2=new MyThread(m); t1.setName("t1"); t2.setName("t2"); t1.start(); try{Thread.sleep(1000); } catch (Exception e){} t2.start(); } } class MyThread extends Thread{ Myclass m=new Myclass(); public MyThread(Myclass m){ this.m=m; } public void run() { if(Thread.currentThread().getName().equals("t1")){ m.doSome(); } if(Thread.currentThread().getName().equals("t2")){ m.doOther(); } } } class Myclass { public synchronized static void doSome() { System.out.println("doSome开始"); try{Thread.sleep(1000*10);} catch (Exception e){} System.out.println("doSome结束"); } public synchronized static void doOther(){ System.out.println("doOther开始"); System.out.println("doOther结束"); } } //synchronized放在静态方法上,是锁类锁的,所以线程t1直接锁住了Myclass类,t2需要等t1的doSome方法结束之后才可以占用Myclass锁
==目前所学的“对象锁”和“类锁”都是排他锁,之后还会有互斥锁==
死锁代码要求会写,面试有时会让你写一个死锁
//死锁 public class Test{ public static void main(String[] args){ Object o1=new Object(); Object o2=new Object(); 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 (Exception e){} 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 (Exception e){} synchronized(o1){ } } } }
所以在开发过程中,synchronized最好不要嵌套使用
- 尽量使用局部变量代替“实例变量”和“静态变量”
- 如果必须使用实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
- 如果不能使用局部变量,对象也不能创建多个,那么只能选择synchronized线程同步机制
除了守护线程(如gc垃圾回收线程),其他都是==用户线程==,当所有的用户线程结束的时候,守护线程也就结束了
//守护线程的实现 class Test{ Thread t=new BakcThread(); t.setDaemon();//设置成守护线程,主线程(用户线程)结束,守护线程也就结束 t.start(); } class BackThread 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(); } } } }
定时器的作用:间隔特定的时间,执行特定的程序
首先可以想到sleep(),不过这种方法是最原始的,现在都是java.util.Timer,不过在实际的开发过程中,目前使用最多的是==Spring框架中提供的SpringTask框架==(其底层原理就是Timer),简单配置即可完成定时器的任务
import java.util.*; import java.text.*; public class Test{ public static void main(String[] args) throws Exception{ Timer timer=new Timer(); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); //Date firstTime=sdf.parse("2022-01-25 21:42:22 222"); Date firstTime=new Date(); timer.schedule(new TimerTask(){int i=0; public void run(){++i; System.out.println("定时器第"+i+"次执行,执行时间 是"+sdf.format(new Date()));} },firstTime,1000*10); } }
TimerTask是一个接口,任务接口,该接口实现了Runnable接口,也是一个线程
定时器的方法:==schedule(TimerTask task,Date date,long time)==
实现Callable接口(jdk8新特性),这种方式==可以获取线程的返回值==,之前的实现Runnable和继承Thread的run方法的返回值都是void,如果希望完成任务的线程结束之后可以返回东西,就需要这种实现了Callable接口的线程实现方式。
实现过程见以下代码:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadThird { public static void main(String[] args) { FutureTask task=new FutureTask(new Callable() { @Override public Object call() throws Exception {//call方法相当于是run方法,只不过有返回值 System.out.println("call method begin"); Thread.sleep(1000*3); System.out.println("call method end"); return 100+200; } }); Thread t=new Thread(task); t.start(); //那么如何获得线程的返回值呢? try { Object o=task.get(); System.out.println(o); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("aaa");//这里的代码需要等t线程结束才可以运行,所以get()方法会导致当前线程阻塞 } }
线程实现的第三种方法的优缺点:
- 优点:可以获取线程的执行结果
- 缺点:效率比较低,get()方法会让当前线程阻塞,效率较低
wait方法和notify方法,不是线程的方法,是java中任何对象的方法,因为是Object的方法
wait()的作用:
Object o=new Object(); o.wait();//让正在o对象上活动的线程进入等待状态,无期限等待,直到唤醒为止
notify()的作用:o.notify() 唤醒o对象上等待的线程
notifyAll()的作用:唤醒o对象上所有等待的线程
==生产者和消费者模式==
注意:o.wait() 让o对象上活动的线程t进入等待状态,并释放掉t线程之前占有的o对象的锁
o.notify() 让o对象上等待的锁唤醒,但是不释放之前占有o对象的锁
//KuCun 对象是共享对象,对于生产者产品为偶数,或满100个就停止生产,对于消费者为奇数或为0时停止消费 public class ShengChanZheXiaoFeiZhe { public static void main(String[] args) { KuCun c=new KuCun(50); Thread ts=new ThreadShengChan(c); Thread tx=new ThreadXiaoFei(c); ts.setName("生产者线程"); tx.setName("消费者线程"); ts.start(); tx.start(); } } class ThreadShengChan extends Thread{ KuCun c; public ThreadShengChan(KuCun c ) { this.c = c; } @Override public void run() { while(true){ synchronized (c){ if(c.getC()%2==0||(c.getC()==100)){ try { c.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } c.setC(c.getC()+1);; try { Thread.sleep(1000*2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"当前库存---->"+c.getC()); c.notifyAll(); } } } } class ThreadXiaoFei extends Thread{ KuCun c; public ThreadXiaoFei(KuCun c) { this.c = c; } @Override public void run() { while (true){ synchronized (c){ if (c.getC()==0||(c.getC()%3==0)){ try { c.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } c.setC(c.getC()-2); try { Thread.sleep(1000*2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"当前库存---->"+c.getC()); c.notifyAll(); } } } } class KuCun{ private Integer c; public KuCun(Integer c){ this.c=c; } public Integer getC() { return c; } public void setC(Integer c) { this.c = c; } }
java.lang.reflect.*;
java.lang.Class 代表字节码文件 代表一个类型
java.lang.reflect.Method 代表字节码中的方法字节码
java.lang.reflect.Constructor 代表字节码中的构造方法字节码
java.lang.reflect.Field 代表字节码中的属性字节码
/*方式1 Class.forName() 1、静态方法 2、方法的参数是一个字符串,并且字符串是一个完整的类名 3、完整类名必须带有包名,不可省略 */ class Test{ public static void main(String[] args){ Class c1=Class.forName("java.lang.String");//c1代表的就是java/lang/String.class Class c2=Class.forName("java.util.Date"); Class c3=Class.forName("java.lang.System"); } } /*方式2 java中任何一个对象都有一个方法:getClass() */ String s="abc"; Class c=s.getClass();//Class 内存储的内存地址是加载到方法区的String字节码文件 /*方式3 java语言中任何一种类型,包括基本数据类型,他都有.class属性 如String.class */
Class c=Class.forName("java.lang.String"); String s=c.newInstance("aaa");//newInstance会调用User这个类的无参数构造方法,不会调用有参构造 //newInstance已经过时了 从jdk9开始
反射机制更加灵活,灵活在哪里呢?
配合属性配置文件和流进行实现
Spring SpringMVC MyBatis
Spring Structs Hibernate
这个方法会导致类加载,从而会执行静态代码块的内容,所以以后只要希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName()
以Module为根得到的相对路径==移植性==太差
这里说一种即使代码换了位置,路径也是通用的方式,前提是:这个文件必须在类路径下【==凡是在src下的路径都是类路径==】
String path=Thread.currentThread().getContextClassLoader().getResource("").getPath(); InputStream in=new FileInputStream(path); Properties pro=new Properties(); pro.load(in); in.close(); String className=pro.getProperty("className"); InputStream reader=Thread.currenThread().getContextClassLoader().getResourceAsStream(""); /* getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象 getResource()是类加载器对象的方法,当前线程的类加载器默认从类的根路径加载资源 */
java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容
同样的,这里的属性配置文件也要放在类路径下,资源绑定器只能绑定==.properties文件==
ResourceBundle bundle=ResourceBundle.getBundle("filename") //文件名不加后缀properties String className=bundle.getString("文件中的Key")
类加载器就是专门负责加载类的命令/工具(ClassLoader)
JDK中自带的3中类加载器
启动类加载器
代码再开始执行之前,会将所有需要的类加载到JVM中,首先通过”启动类加载器“加载,加载的是==jre\lib\rt.jar==内的类。
扩展类加载器
如果启动类加载器加载不到的时候,就是用扩展类加载器,加载的是==jre\lib\ext==目录下的包
应用类加载器
如果启动类加载器和扩展类加载器都加载不出来,就会启动应用类加载器,也就是在classpath下找对应的包资源
==双亲委派机制==
启动类加载器 String
扩展类加载器
应用类加载器 String(你自己写了一个String类,但是JVM加载不到)
==java中为了保证类加载的安全,使用了双亲委派机制,“父”是启动类加载器,“母”是扩展类加载器==,如果都加载不到,就去应用类加载器中加载,直到加载到为止
//********************获取属性 //先获取整个类 Class class=Class.forName("类名"); class.getName();//获取完整类名 class.getSimpleName();//获取简类名 //获取类中所有的Field Field[] fields=class.getFields();//获取的是所有public的属性 Field f=fields[0]; String fieldName=f.getName(); Field[] fields=class.getDeclaredFields();//获取所有属性 //*********************获取属性的类型 Field对象是包括修饰符列表的 如:pirvate String name; Class fieldType=f.getType(); String fieldName=fieldType.getName(); //*********************获取属性的修饰符列表 String modifiersName=Modifier.toString(f.getModifiers());//getModifiers的返回值是int,实际是每个修饰符的代号,怎么将代号转换成字符串呢?使用到Modifier类中的静态方法------toString(int mod)即可
获取单个属性
//通过反射机制调用参数 Class c=Class.forName("className"); Object obj=c.newInstance(); Field noFiedl=c.getDeclaredField("no"); noField.set(obj,222); noField.get(obj);//获取对应属性的值 如果属性的值是private修饰的 那么只能打破封装 //打破封装代码 noField.setAccessible(true);
==可变长参数==:以一个静态方法为例
public static void main(String[] args){m(1);m(1,2);m(12,3,2);} public static void m(int... args){System.out.println("m方法执行了!")} //可变长参数只能出现在参数列表的最后一个,并且可变长参数一个参数列表里只能有一个 //可以把可变长参数看成 一个数组 它也有Length属性 public static void m1(String... args){ for(int i=0;i<args.length;i++){ System.out.println(args[i]); } }
==如何反射Method==
Method[] method=Class.forName("").getDeclaredMethods(); Method m=method[0]; m.getReturnType().getSimpleName();//返回值类型 m.getName();//返回方法名 m.getParameterTypes();//获取方法的修饰符列表
//通过反射调用方法 Class c=Class.forName(""); Object obj=c.newInstance(); Method login=c.getDeclaredMethod("方法名",Class<?>... agrs)//这里传入的是参数的类型 Object retValue=login.invoke(obj,"admin","123"); //反射方法四要素 //1、login方法 //2、obj对象 //3、admin 123 实参 //4、retValue 返回值
//通过反射机制创建对象 使用构造方法 newInstance()的调用有参构造 Class c=Class.forName(""); Constuctor con=c.getDelcaredConstructor(对应的形参类型); Object obj=con.newInstance(对应的实参); //newInstance()调用无参构造的正确方式 Object newObj=con.newInstance();
Class stringClass=Class.forName("java.lang.String"); Class supoerClass=stringClass.getSuperclass(); //getSuperclass //获取所有的接口 Class[] interfaces=stringClass.getInterfaces();
noFiedl.setAccessible(true);容易打破封装,给不法分子留下机会
/* 1、注解是一种引用数据类型,编译之后也生成xxx.class文件 2、定义注释的语法格式: [修饰符列表] @interface 注解类型名{} 3、注解怎么使用? @注解类姓名 注解可以出现在类上、属性上、方法上、变量上,还可以出现在注解上 */ public @interface MyAnnocation{ } @MyAnnocation class Dosome{ @MyAnnocation int a; @MyAnnocation public static void dosome(){} @MyAnnocation public void doOther(){@MyAnnocation int a;} } @MyAnnocation interface Myinterface{} @MyAnnocation enum Season{} @MyAnnocation @interface OtherAnnocation{}
java.lang报下的注解
Deprceated 已过时
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { String since() default ""; boolean forRemoval() default false; }
Override 方法覆盖
override这个注解是给编译器看的,和运行阶段无关,凡是带有这个注解的方法,编译阶段都要检查是否重写了父类的方法,如果没有编译器就会报错。
//Override注解的源代码 @Target(ElementType.METHOD)//这两个标注“注解类型”的“注解”称为元注解 @Retention(RetentionPolicy.SOURCE) public @interface Override{} //Target注解 用来标注“被标注注解”可以出现在哪些位置上 //Retention注解 用来标注“被标注注解”最终保存到哪 @Retention(RetentionPolicy.SOURCE)//表示该注解被保留在java源文件中 @Retention(RetentionPolicy.CLASS)//表示该注解被保留在class文件中 @Retention(RetentionPolicy.RUNTIME)//表示该注解被保留在class文件中,并且可以通过反射机制进行读取
@interface MyAnnotation{ String name() default "ss";//如果这里的属性名是value,并且只有一个属性,那么使用该注解的时候,赋值可以省略属性名 String[] ss(); Sesson[] season(); } public class Test{ @MyAnnotation(name="sss",ss="ssss")//数组只有一个元素{}可以省略 public void doSome(){} }
注解的属性类型可以是:byte short int lang float double boolean char ==String Class 枚举类型==,以及这些类型的数组形式
import java.lang.annotation.*; public class ReflectAnnotation{ public static void main(String[] args) throws Exception{ Class c= Class.forName("com.wzx.reflect.MyAnnotation"); System.out.println(c.isAnnotationPresent(MyAnnotation.class));//看看这个类上是否有注解 } } @MyAnnotation @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{} //反射得到注解对象 Class c= Class.forName("com.wzx.reflect.MyAnnotation"); System.out.println(c.isAnnotationPresent(MyAnnotation.class)); if (c.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation myAnnotation= (MyAnnotation) c.getAnnotation(MyAnnotation.class); System.out.println(myAnnotation); } //注解的属性可以直接 引用点 //通过反射获取注解对象属性的值 public class Test{ @MyAnnotation(username="admin",password="123") public void doSome(){} public static void main(String args){ //获取Test的doSome方法上的注解信息 Class c=Class.forName("Test"); Method method=c.getDeclaredMethod("doSome"); if(method.isAnnotationPresent(MyAnnotation.class)){ System.out.println(method.Annotation.username+method().Annotation.password()); } } }
需求:假设有一个叫id的注解,要求这个注解只能出现在类上,并且类有这个注解的时候必须有一个int类习惯的id,如果没有就报错,如果有则正常执行!
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; public class AnnotationTest { public static void main(String[] args) throws Exception{ try { panDuan("com.wzx.reflect.User1"); }catch (MyException me){ System.out.println(me.getMessage()); } } public static void panDuan(String add) throws ClassNotFoundException, MyException { Class userClass=Class.forName(add); if (userClass.isAnnotationPresent(ID.class)){ //当一个类有@ID注解的时候 要判断这个类里是否有int id Field[] fields=userClass.getDeclaredFields(); for (Field field:fields){ if ("id".equals(field.getName())&&("int".equals(field.getType().getSimpleName()))){ System.out.println("语法正确"); return; } } throw new MyException("语法错误"); } } } class MyException extends Exception{ public MyException() { } public MyException(String message) { super(message); } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface ID } @ID class User1{ int id; String name; String password; }
注解在开发中到底有什么用?
类型转换异常 java.lang.ClassCastException
空指针异常 java.lang.NullPointException
数组越界异常 java.lang.ArrayIndexOutOfBoundsException
数字格式化异常 java.lang.NumberFormatException
分母为零异常 java.lang.ArithmeticException
集合内容发生变化,没有重新获取iterator时 java.util.ConcurrentModificationException ==因为迭代器类似一个照相机,看到的都是集合获取迭代器时候的集合内容==
==和equals的区别
==是对内存地址进行对比
equals是对对象的内容进行对比
数组拷贝:==System.arrayCopy==(==源目标==,==源开始下标==,==目标==,==目标开始下表==,==拷贝长度==)
获取当前毫秒数(自1970年0000 至今):==System.currentTimeMillis()==;
建议启动垃圾回收:==System.gc()==
推出JVM:==System.exit(0)==
==Arrays:==
排序:Arrays.sort(int[] a);
二分查找:Arrays.binarySearch(int[] a,对应元素); 如果返回结果是-1说明不存在改元素
==String:==
- char charAt(int index ) "中国人".charAt(1) ----> "国"
- int compareTo(String anotherString) 两个字符串相等即为0 其他看大小 差值
- boolean contains(CharSequence s)
- boolean endWith(String suffix)
- startsWith
- boolean equalsIgnoreCase(String anotherString) 判断两个字符串是否相等,并且不区分大小写
- String toLowerCase() 转换到小写
- toUpperCase()
- String trim() 去除字符串前后空白
- byte[ ] getByte(String s) 将字符串转成byte数组
- char[ ] toCharArray() 将字符串转成char数组
- int indexOf(String s) 判断s子字符串在某个串中第一次出现处的索引
- lastIndexOf
- boolean isEmpty() 判断字符串是否为空
- int leagth() 判断数组长度是length属性,判断字符串长度是length()方法
- String replace(charSquence target,charSquence replacement)
- String[ ] split(String regex) "1998-02-16".split("-");
- String subString(int beginIndex) 截取字符串
- String subString(int beginIndex,int endIndex) 截取字符串 包括beginIndex 不包括endIndex
- ==静态方法== String.valueOf(非字符串) 将非字符串转换成字符串
==Integer==
- static int parseInt(String s) 字符串转换成int 所以也有parseDouble
==引用.valueOf()== 将括号内的类型转换成 对应的包装类
字符串从出生到死亡都是不能变的
==在JDK中双引号括起来的字符串都是放在方法区的字符串常量池的==为了提高执行效率
String s=new String("xy"); //双引号的字符串都是在方法区的字符串常量池中,这里是在堆中创建对象,并且指向字符串常量池中的"xy"
==垃圾回收器是不会释放常量的==
String类重写了equals方法,所以对于字符串对象的相同比较直接使用equals方法即可
//以下程序创建了几个对象 public class Test{ public static void main(String[] args){ String a=new String("aaa"); String b=new String("aaa"); } } //以上程序一共创建了3个对象 分别是一个字符串常量池对象和两个堆String对象
String的构造方法可以使用==byte[],char[]==
思考:在实际开发中,如果需要频繁对字符串进行拼接,有什么问题?
==在对字符串进行 拼接得时候,每一次拼接都会产生新字符串,从而方法区占用内存,造成空间浪费==
如果需要频繁进行字符串拼接,要使用java自带得类:java.lang.StringBuffer和java.lang.StringBuilder
==StringBuffer(线程安全的)==
初始化容量是16(字符串缓冲对象)
StringBuffer的底层是byte数组 ==String的底层也是byte数组,不过是被final修饰了的byte数组==
StringBuffer中有一个方法,可以追加字符串 即==append== 这个append方法已经被==重载==可以传入很多参数类型
==如何优化Stringbuffer的性能==
在创建StringBuffer的时候尽可能给定一个初始化容量,最好减少底层数组的扩容次数
==Stringbuilder==
- 初始容量也是16
基本数据类型 包装类型 byte java.lang.Byte short java.lang.Short int java.lang.Integer long java.lang.Long float java.lang.Float double java.lang.Double boolean java.lang.Boolean char java.lang.Character 八种包装类型中,Boolean和Character的父类是java.lang.Object,其他的都是java.lang.Number
Number类有多个方法,Number类是一个抽象类,不可以实例化对象
- intValue floatValue doubleValue byteValue longValue shortValue ==这几个方法都是负责拆箱的==
==基本数据类型 ---> 引用数据类型(装箱)==
==引用数据类型 ---> 基本数据类型(拆箱)==
==Integer==
Integer的构造方法可以装箱String public Integer(String s)
Integer的最大值和最小值 Integer.MAX_VALUE 最小值Integer.MIN_VALUE
==自动装箱== Integer i=10; ==自动拆箱== int a=i;
有了自动拆箱之后 Number类中的方法就用不着了
==Integer类型的i +1 会自动将Integer转换成int类型, 自动拆箱机制== 运算才会拆箱 ==不会拆箱
==Java为了提高执行效率,将-128----127之间的包装对象提前创建好,挡在了"整数型常量池"中==这个整数型常量池在Integer类加载的时候就会实现
==缓存== 优点:效率高 缺点:耗费内存 大型项目的重要优化手段就是cache缓存机制
//String int 和Integer之间的转换 class Test { public static void main(String[] agrs){ //String ----> int String s1="100"; int i1=Integer.parseInt(s1); //int ----->String String s2=String.valueOf(i1); String s3=i1+" "; //int -->Integer Integer ie=i1; //Integer-->int int i2=ie; //String--->Integer Integer ie1=Integer.valueOf(s1); //Integer--->String String s4=String.valueOf(ie1); } }
java.util.Date
SimpleDateFormat 专门用于日期格式化的类 java.text包下的
import java.util.Date; import java.text.SimpleDateFormat; class Test { public static void main(String[] args){ Date nowTime=new Date(); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); System.out.println(sdf.format(nowTime)); } } //Date--->String
以上是将日期对象转换成了字符串
如何将字符串转化成对应的Date对象 使用==SimpleDateFormat.parse==方法
String time="2009-12-30 08:08:08 888"; SimpleDateFormate sdf=new SimpleDateFormate("yyyy-MM-dd hh:MM:ss SSS"); Date d=sdf.parse(time); //String ---> Date
获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数
System.currentTimeMillis();
Date d=new Date(System.currentTimeMillis()); // 毫秒 ---> Date
数字格式化
java.text.DecimalFormat专门负责数字格式化
- #代表任何数字 ,代表千分位 . 代表小数点 0代表不够的时候补0
数字格式化和日期格式化
DecimalFormat df=new DecimalFormat("###,###.0000") df.format();
==BigDecimal==属于大数据,精度极高,用在财务软件当中
BigDecimal v1=new BigDecimal(100); BigDecimal v2=new BigDecimal(200); System.out.println(v1.add(v2)); //BigDecimal的toString重写了 subtract乘法 divide除法
Random random=new Random();//创建随机数对象 int num=random.nextInt();//随机产生一个int类型取值范围内的数字 还有重载的nextInt()形参输入对应的int边界,范围不包含形参中 System.out.println(num);
为什么要用枚举,因为boolean类型无法再满足需求,可能性在两个之上
==如果要判断的结果有多种可能性,那么建议使用枚举==
- ==枚举,一枚一枚可以列举出来的,才建议使用枚举类型==
- ==枚举编译之后生成的也是class文件==
- ==枚举是一种引用数据类型==
- ==枚举中的每个值都可以看作是常量==
class Test{ public static Result divide(int a,int b){ try{ int c=a/b; return Result.SUCCESS; }catch(Exception e){ return Result.FAIL; } } } enum Result{ SUCCESS,FAIL }
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。