JAVA学习中记录的一些笔记,不断更新
目录
1.标识符
包名:xxxyyyzzz
类名接口名:XxxYyyZzz
变量名函数名:xxxYyyZzz
常量名:XXX_YYY_ZZZ
2.变量
基本数据类型:数据型,字符型,布尔型(栈内存)
引用数据类型:类,接口,数组(栈内存引用堆内存)
当数据不确定时,且需要对数据进行存储时,定义变量
3.逻辑运算符
&和&&:
&:无论左边是true还是false,右边都运算
&&:当左边为false时,右边不运算
|和||
|:两边都参与运算
||:当左边为true时,右边不运算
4.位运算符(高效运算)
左移<<:乘2的移动位数次幂
右移>>:除2的移动位数次幂
>>:最高位补什么由原有数据的最高位值而定
如果最高位0,右移后,用0补空位
如果最高位1,右移后,用1补空位
>>>:无论最高位是什么,右移后,都用0补
异或^:A^B^B=A(加密解密,数据交换)
5.语句
switch:判断的具体数值不多,且符合byte,short,int,char,String(1.7)类型
if:对区间判断,对结果为Boolean类型判断
for:变量有作用域,循环增量定义在for中,且只在for中有效,for执行完毕后,该增量在内存中消失。for(;;){}-->(无限循环)
while,dowhile:运算结果用于循环外,while(true){}-->(无限循环)
break:跳出,应用于选择结构和循环结构
continue:继续,应用于循环结构,结束本次循环继续下次循环
6.函数(方法)
重载(overload):同一个类中,同名且参数个数或参数类型不同,与返回值类型无关。当定义的功能相同,但参与运算的位置内容不同,定义一个函数名称以表示该功能,通过参数列表的不同来区分多个同名函数。
主函数(main 不是关键字):是一个特殊的函数,作为程序入口,可以被JVM调用
public static void main(String[] args)args==arguments(n.参数)
public:代表该函数访问权限最大
static:代表主函数随着类的加载就已经存在了
void:主函数没有具体的返回值
String [] args:函数的参数,参数类型是一个字符串数组,传入的是new String[0] 空数组
javac mainDemo
java mainDemo haha hehe heihei
System.out.println(args[0]);--->haha
7.内存结构
栈内存:用于存储局部变量,当数据使用后,所占空间会自动释放
堆内存:数组和对象,通过new建立的实例都存放在堆内存中,每一个实体都有内存地址值,实体中的变量都有默认初始化值,实体不在被使用,会在不确定的时间内被垃圾回收器回收。
[I@xxxxxx:一维数组,integer类型元素,内存地址(哈希值,十六进制)
方法区:
本地方法区:
寄存器:
8.数组
排序:Arrays.sort(arr);
选择排序:内循环结束一次,最值出现在头角标位上
public static void selectSort(int []arr)
{
for(int x=0;x<arr.length;x++)
{
for(int y=x+1;y<arr.length;y++)
{
if(arr[x]>arr[y])
{
int temp=arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
}
}
}
冒泡排序:
public static void bubbleSort(int []arr)
{
for(int x=0;x<arr.length-1;x++)
{
for(int y=0;y<arr.length-x-1;y++)//-x:让每一次比较的元素减少 -1:避免角标越界
{
if(arr[y]>arr[y+1])
{
int temp=arr[y];
arr[y]=arr[y+1];
arr[y+1]=temp;
}
}
}
}
9.面向对象
封装,继承,多态
具体对象就是java在堆内存用new建立实体
属性就是类中变量,行为就是类中函数(成员变量和成员方法)
类类型变量指向堆内存中的对象
成员变量和局部变量 作用范围
成员变量作用于整个类中,存放于堆内存,因为对象的存在,才在内存中存在
局部变量作用于函数中,或者语句中,存放于栈内存中
10.匿名对象
当对对象的方法(非属性变量,调用完即在内存中成为垃圾,等待回收)只调用一次时,可简化为匿名对象
可以将匿名对象作为实际参数进行传递
Car c=new Car();
show(c);
show(new Car());
11.访问控制修饰符
用于控制被修饰变量、方法、类的可见范围.
public 的访问级别是最高的,其次是 protected、默认和 private.
成员变量和成员方法可以处于4个访问级别中的一个:公开、受保护、默认或私有.
存在继承关系时,父类不可以是 private,因为子类无法继承
顶层类可以处于公开或默认级别,顶层类不能被 protected 和 private 修饰. (外部类)
局部变量不能被访问控制修饰符修饰
private:私有,用于修饰类中成员(成员变量和成员方法),私有只在本类中有效,对象不能直接访问,提供get,set方法进行访问。仅仅是封装的一种表现形式
默认权限(不写default):介于私有和共有之间
public:
protected:
图转自 https://blog.csdn.net/Xk632172748/article/details/51755438
12.构造函数
默认构造函数(系统自带): 类名(){} 默认构造函数的权限随着类的变化而变化
自定义构造函数后,默认构造函数消失
对象一建立,就会调用与之对应的构造函数,可以用于给对象进行初始化,只运行一次
构造代码块 {}
作用:给对象进行初始化 对象一建立就运行,而且优先于构造函数执行
构造代码块是给所有对象进行统一初始化(定义不同对象共性的初始化内容)
构造函数是给对应的对象初始化
13.this关键字
代表对本类对象的引用
默认被省略
用于区分局部变量和成员变量同名的情况
代表它所在函数所属对象的引用
当定义类中功能(方法)时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象
但凡本类功能内部使用了本类对象,都用this表示
this语句:只能用于在构造函数间进行互相调用,this语句只能定义在构造函数第一行,因为初始化要先执行
Person()
{
this.name="xixi";
}
Person(String name)
{
this();
this.name=name;
}
Person(String name,int age)
{
this(name);
//this.name=name;
this.age=age;
}
14.static关键字
静态 用于修饰成员(成员变量,成员函数),成员被静态修饰后,成员不存在于堆内存中,而存在于方法区(共享区,数据区),在内存中只有一个副本
被修饰的成员:随着类的加载而加载,优先于对象存在,被所有对象所共享,共享数据 (country=“CN”(类变量))
可以直接被类名调用(类名.静态成员),因为静态成员存在时,对象可能不存在。
注意事项:静态方法只能访问静态成员,非静态方法既可以访问静态也可以访问非静态,
静态方法中不可以写this,super关键字,主函数是静态的
利处:对对象的共享数据进行单独空间存储,节省空间,没有必要每个对象都存储一份。
弊处:生命周期过长,访问出现局限性
用处:当对象中出现共享数据(值)时,该数据被静态修饰,对象特有数据定义成非静态。
当功能内部没有访问到非静态数据时,该功能可以定义成静态方法
应用:每一个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。
工具类(ArrayTool等)不需要对象,可将类中方法定义成静态的,直接通过类名调用。因为不需要建立对象,可将构造函数私有化强制不能建立对象。
静态代码块: static{ 语句 } 随着类的加载而执行,只执行一次。用于给类进行初始化。
15.对象的初始化过程
class Person
{
private String name="xixi";
private int age;
private static String country="CN";
Person (String name,int age)
{
this.name=name;
this.age=age;
}
{
System.out.println(name+"--"+age);
}
public void setName(String name)
{
this.name=name;
}
public void speak()
{
System.out.println(this.name+"--"+this.age);
}
public static void showcountry()
{
System.out.println("country="+country);
}
}
class PersonDemo
{
public static void main(String []args)
{
Person p=new Person("heihei",10);
}
}
Person p=new Person("heihei",10);
1.因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中(Person p=null 这种情况类不加载(new时会用到构造函数))
2.执行该类中的static代码块,如果有的话,给Person.class进行初始化。
3.在堆内存中开辟空间,分配内存地址。
4.在堆内存中建立对象的特有属性,并进行默认初始化。
5.对属性进行显示初始化。
6.对对象进行构造代码块初始化。
7.对对象进行对应的构造函数初始化。
8.将内存地址赋给栈内存中的p变量。
16.继承
特性:单继承(多继承安全隐患),支持多层继承
聚集 :has a
聚合:球队球员
组合:人体器官
提高代码的复用性,让类与类之间产生了关系,有了这个关系,才有了多态的特性。所属关系 :is a
如何使用一个继承体系中的功能:先查阅体系父类的描述,因为父类中定义的是该体系中共性功能,通过了解共性功能。就可以知道该体系的基本功能。在具体调用时,要创建最子类的对象,因为有可能父类不能创建对象(抽象类),创建子类对象可以使用更多功能。
当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖,保留父类的功能定义,并重写功能内容。
子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。(方法非属性)静态只能覆盖静态。
如果子类和父类中有同名属性,父类引用指向子类对象的时候,通过父类引用访问那个同名属性时,访问的是父类中的那个,不是子类中的那个,因为父类引用是看不到子类中的属性的,这和方法不同,父类引用调用子类方法时有个优先级的问题,this优先于super,即,如果子类中重写了父类的方法,那么调用的就是子类中的,如果没有重写,就调用的是父类中的。
public class tt
{
public static void main(String[] args) throws Exception{
A b=new B(); //父类引用指向子类对象
b.show();//输出bbbb
System.out.print(b.num);//输出111
int x=b.getnum();
System.out.print(x);//输出333
b.ss();//输出AAAA
}
}
class A
{
public int num=111;
public void show()
{
System.out.print("aaaa");
}
public int getnum() {
return num;
}
public void ss()
{
System.out.print("AAAA");
}
}
class B extends A
{
public int num=333;
public int getnum()
{
return num;
}
public void show()
{
System.out.print("bbbb");
}
}
17.super关键字
代表对父类对象的引用
在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式语句 super();
super():会访问父类中空参数的构造函数,而且子类中所有的构造函数默认第一行都是super();
因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类时如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。如果要访问父类中指定的构造函数,可以通过手动定义super语句的方法来指定。当然,子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少会有一个构造函数会访问父类中的构造函数。
18.final关键字
final可以修饰类,方法,变量。
final修饰的类不可以被继承。
final修饰的方法不可以被覆盖。
final修饰的变量是一个常量,只能被赋值一次。
内部类只能访问被final修饰的局部变量。
19.abstract关键字
当多个类中出现相同功能,但是功能主体不同。这时可以进行向上抽取,这时,只抽取功能定义,而不抽取功能主体。
抽象类的特点:抽象方法一定在抽象类中,抽象方法和抽象类都必须被abstract关键字修饰,抽象类不可以用new创建对象,因为调用抽象方法没意义。抽象类中的方法要被使用必须由子类复写其所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
20.接口
可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示
不可以创建对象,因为有抽象方法,需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化,否则子类是一个抽象类。
一个类可以实现多个接口也是对多继承不支持的转换形式
class用于定义类
interface用于定义接口
接口定义时,格式特点:
接口中常见定义:常量,抽象方法
接口中的成员都有固定修饰符(自动+):常量:public static final
方法:public abstract
接口中的成员都是public
类与类 继承 extends
类与接口 实现 implements
接口与接口 继承 extends(接口可以多继承)
21.多态
可以理解为事物存在的多种体现形态
猫 x=new 猫();
动物 x=new 猫();//类型提升,向上转型
体现:父类引用指向了自己的子类对象,父类引用也可以接收自己的子类对象
前提:类与类之间必须有关系,要么继承,要么实现
好处:提高程序的扩展性
弊端:只能使用父类的引用访问父类中的成员。
如果子类和父类中有同名属性,父类引用指向子类对象的时候,通过父类引用访问那个同名属性时,访问的是父类中的那个,不是子类中的那个,因为父类引用是看不到子类中的属性的,这和方法不同,父类引用调用子类方法时有个优先级的问题,this优先于super,即,如果子类中重写了父类的方法,那么调用的就是子类中的,如果没有重写,就调用的是父类中的。
//如果想要调用子类的特有方法,就强制将父类的引用,转成子类类型
Animal a=new Cat();//向上转型
a.eat();
Cat c=(Cat)a;//向下转型
c.catchMouse();
//能转换的是父类引用指向自己的子类对象时,该引用可以被提升,也可以被强制转换
//多态自始至终都是子类对象在做着变化
Animal a=new Animal();
Cat c=(Cat)a;//这是不允许的
a instanceof Cat//判断a是否是Cat类型(判断所属类型)
在多态中成员函数(非静态)的特点:
在编译时期,参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,否则编译失败
在运行时期,参阅对象所属的类中是否有调用的方法
简单总结:非静态成员函数在多态调用时,编译看左边,运行看右边
静态成员函数,无论编译和运行,都参考左边
成员变量,无论编译和运行,都参考左边(引用型变量所属的类)
22.内部类
定义原则:当描述事物时,事物的内部还有事物,该事物用内部类来描述。因为内部事物在使用外部事物的内容。
内部类可以直接访问外部类中的成员,包括私有(因为内部类有默认引用 外部类.this)
外部类要访问内部类,必须建立内部类对象
其他类直接访问内部类: Outer.Inner in= new Outer().new Inner();
外部类与内部类出现同名变量时,内部类想要访问外部类变量:Outer.this.xxx;
内部类在成员位置上,就可以被成员修饰符所修饰
内部类定义在局部时,不可以被成员修饰符修饰
访问局部变量时,局部变量必须被final修饰
private :将内部类在外部类中进行封装
static:内部类具有静态特性(静态内部类),只能直接访问外部类中的静态成员
当内部类中定义了静态成员,该内部类必须是静态的
当外部类中的静态方法访问内部类时,内部类也必须是静态的
其他类访问静态内部类的非静态成员 new Outer.Inner().function();
其他类访问静态内部类的静态成员 Outer.Inner().function();
23.匿名内部类
匿名内部类其实就是内部类的简写格式。是一个匿名子类对象
前提:内部类必须是继承一个类或者实现接口
匿名内部类中定义的方法最好不要超过三个
abstract class AbsDemo
{
abstract void show();
}
class Outer
{
int x=3;
/*
class Inner extends AbsDemo
{
void show()
{
System.out.println(x);
}
}
*/
public void function
{
//new Inner().show();
new AbsDemo()//创建的是AbsDemo的子类,因为show方法被重写了
{
void show()
{
System.out.println(x);
}
}.show();
}
}
interface Inter
{
void method();
}
class Test
{
public static Inter function()//1
{
return new Inter()
{
public void method()
{
System.out.println("haha");
}
};
}
}
class TestDemo
{
public static void main(String []args)
{
Test.function().method();//1
show(new Inter()
{
public void method()
{
System.out.println("haha");
}
});//2
}
public static void show(Inter in)//2
{
in.method();
}
}
24.异常
程序在运行时出现的不正常情况
异常由来:问题也是现实生活中一个具体的事物,也可以通过JAVA的类的形式进行描述,并封装成对象
其实就是java对不正常情况进行描述后的对象体现
问题划分:严重的 java通过Error类进行描述(一般不编写针对性的代码对其进行处理)
非严重的 java通过Exception类进行描述(可以使用针对性的处理方式进行处理)
向上抽取 父类 Throwable
异常的处理:
try{ 需要被检测的代码 }
catch( 异常类 变量 ){ 处理异常的代码(处理方式) } (Exception e = new XXXException())
finally{ 一定会执行的语句 通常用于关闭资源(IO流 SQL连接) }
声明异常:throws Exception 必须有try catch回应,否则编译失败,除非一直抛,直到抛给JVM处理。
声明异常时,简易声明更具体的异常 如除零 ArithmeticException
对方声明几个异常,就对应有几个catch块,不要定义多于的catch块。
如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。
建议在进行catch处理时,一定要定义具体处理方式,不要简单定义一句e.printStackTrace语句。
自定义异常
因为项目中会出现特有的问题。而这些问题并未被java所描述并封装成对象。 所以对于这些特有的问题可以按照java的对问题封装的思想,将特有的问题,进行自定义的异常封装。
当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作,要么在内部try catch 要么在函数上声明让调用者处理。
一般情况下,函数内出现异常,函数上需要声明。
如何定义异常信息:因为父类已经把异常信息的操作都完成了,所以子类只要在构造时,把异常信息传递给父类通过super语句。那么就可以直接通过getMessage方法获取自定义的异常信息。
自定义异常,必须是自定义类继承Exception
异常体系有一个特点,因为异常类和异常对象都被抛出,他们都具备可抛性,这个可抛性是throwable这个体系中的独有特点。
只有这个体系中的类和对象才可以被throw和throws操作。
throw和throws的区别:
throws使用在函数上 throw使用在函数内
throws后面跟的是异常类,可以跟多个,用逗号隔开 throw后面跟的是异常对象
class FuShuException extends Exception
{
private int value;
FuShuException()
{
super();
}
FuShuException(String message,int value)
{
super(message);
this.value=value;
}
public int getValue()
{
return value;
}
}
class Demo
{
int div(int a,int b)throws FuShuException
{
if(b<0)
throw new FuShuException("出现了除负数异常!!!",b);//手动通过throw关键字抛出自定义异常
return a/b;
}
}
class Test
{
public static void main(String []args)
{
Demo d=new Demo();
try
{
int x=d.div(4,-9);
System.out.println(x);
}
catch(FuShuException e)
{
System.out.println(e.toString());
System.out.println("错误的负数是"+e.getValue());
}
}
}
特殊异常:RuntimeException
Exception中有一个特殊的子类异常RuntimeException运行时异常
如果在函数内中抛出异常,函数可以不用声明,编译一样通过。
如果在函数上声明了该异常,调用者可以不进行处理,编译一样通过。
之所以不用函数声明,是因为不需要让调用者处理,当该异常发生,希望程序停止,因为在运行时,出现了无法继续运算的情况,希望停止程序后,对代码进行修正。
自定义异常时,如果该异常的发生,无法在继续进行运算,就让自定义异常继承RuntimeException.
异常分两种
编译时被检测的异常
编译时不被检测的异常(RuntimeException及其子类)
异常在子父类覆盖中的体现:
1 子类在覆盖父类方法时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类,或者不抛(自己捕获异常并解决)
2 如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集
3 如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常(如果子类方法发生了异常,就必须进行try处理,绝对不能抛)
总结
Ps:截图为毕向东老师Java基础视频。
25.包
对类文件进行分类管理,给类提供多层命名空间,写在程序文件的第一行,类名的全称是 包名.类名,包也是一种封装形式。
编译:javac -d . PakeageDemo.java(-d 参数 包文件夹地址 (.为当前地址))
运行:java pack.PakeageDemo
总结:包与包之间进行访问,被访问的包中的类以及类中的成员,需要被public修饰
不同包中的子类还可以直接访问父类中被protected权限修饰的成员
import:为了简化类名的书写,使用此关键字
//如果不import的话 pack.packzi.DemoB a=new pack.packzi.DemoB();
建议不写通配符* 需要使用哪个类。就导入哪个。
c:\myclass
c:\myclass\pack\DemoA.class
c:\myclass\pack\packzi\DemoB.class
import pack.*; 导入的是pack中的所有类,不包括packzi中的类
import pack.packzi.*; 导入的是packzi中的所有类
定包名不要重复,可以使用url来定义
import cn.itcast.demo
26.多线程
一个程序至少有一个进程,一个进程至少有一个线程
进程:是一个正在执行中的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
Javac 编译时 会启动javac.exe 编译完成后关闭
Java VM 启动的时候会有一个进程java.exe
该进程至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
JVM不止启动一个线程,还有负责垃圾回收机制的线程。
创建线程的第一种方式:继承Thread类
定义类继承Thread 重写Thread类中的run方法,调用线程的start方法(start方法启动线程并调用run方法)。
class Demo extends Thread
{
Demo(String name)
{
super(name);//自定义线程名称
}
public void run()
{
System.out.print(this.getName());
// System.out.print(Thread.currentThread().getName()); Thread.currentThread() 获取当前线程的引用 建议使用这种方法
}
}
main()
{
Demo d=new Demo("xixi");//创建线程
d.start();//启动线程 并调用run方法 则d这个线程和主线程就在“同时”运行(并发),出现随机性,CPU执行谁,谁执行。
//d.run(); 如果直接调用run方法,而没有启动线程,run()中的代码执行完毕后主函数才会继续运行。
}
线程有默认名字 格式:Thread-编号 编号从0开始。
自定义线程名字 :构造函数或者setName()方法
多线程具有随机性,谁抢到谁执行。
线程的几种状态:
创建线程的第二种方式:实现Runnable接口
定义类实现Runnable接口,覆盖Runnable接口中的run方法,通过Thread类建立线程对象,将Runnable接口的子类对象作为
实际参数传递给Thread类的构造函数,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
继承方式和实现方式:建议使用实现方式,避免了单继承的局限性。
区别:继承---线程代码存放在Thread子类run方法中。
实现---线程代码存放在接口子类run方法中。
多线程安全问题: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行时,导致共享数据的错误。
Java解决多线程的安全问题的方法:同步代码块/同步函数
Object obj=new Object();
synchronized(对象){}
哪些代码需要放进同步代码块中:
明确哪些代码是多线程运行代码
明确共享数据
明确多线程运行代码中哪些语句是操作共享数据的
同步函数使用的锁是this
静态同步函数,使用的锁是该方法所在类的字节码文件对象 类名.class(静态方法中不可以定义this 静态进内存 内存中没有本类对象 但是一定有该类对应的字节码文件对象 所以该对象的类型是Class)
同步代码块
同步函数
同步函数
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取了cpu的执行权,也进不去,因为没有获取锁
同步的前提:必须要有两个或者两个以上的线程,必须是多个线程使用一个锁
虽然同步代码块解决了多线程的安全问题,但多个线程都需要判断锁,较为消耗资源。
死锁:同步中含有同步
卖票:
public class TicketSale {
public static void main(String[] args) {
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket extends Thread
{
private static int tick=100;
public void run()
{
while(tick>0)
{
System.out.println(currentThread().getName()+"----"+tick--);
}
}
}
public class TicketSale {
public static void main(String[] args) {
Ticket t=new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable
{
private int tick=100;
Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+"----"+tick--);
}
}
}
}
}
多线程通信: 多个线程在操作同一个资源,但是操作的动作不同
wait() nitify() notifyAll()
都是用在同步中,因为要对持有监视器(锁)的线程操作。只有同步才具有锁。
为什么这些方法定义在Object类:因为这些方法在操作同步中线程时,都必须要标识他们所操作线程只有的锁,只有同一个锁上的被等待线程,才可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中
优化:
生产者消费者:
多个生产者多个消费者
1.在判断flag时,用While不用if(每次被唤醒应重新判断flag 可能会导致生产两个 消费一个等不匹配情况,)
2.应使用notifyAll();(用notify可能导致所有线程都wait()。)
JDK1.5 提供了多线程升级方案
将同步Synchronized替换成显式Lock操作,将Object中的wait,notify,notifyALL替换成了Condition对象,该对象可以Lock锁进行获取,可以实现本方线程只唤醒对方线程的操作。
相关接口:Condition Lock
线程停止:
守护线程(后台线程):当当前线程都为守护线程时,自动结束。
Join:等待线程结束
27.String
字符串特点:一旦被初始化,就不可以被改变。存在于常量池。
String使用private final char value[ ]实现字符串的存储,也就是说String创建对象之后不能够再次修改此对象中存储的字符串内容
String s="abc";//s1是一个类类型变量,"abc"是一个对象
s="123"; //此时"abc"这个对象的内容并没有改变,只是s这个引用指向了另一个对象,即"123"
String类中重写了equals方法用于比较两个字符串的内容是否相等。(Java中也有其他类对equals进行了重写)
一般情况 | String类重写 | |
== | 比较内存地址 | ------ |
equals | 比较内存地址 | 比较内容 |
public class sss {
public static void main(String []args)
{
String s1="aaa";
String s2="aaa";
String s3=new String("aaa");
//s1和s3的区别
s1在内存中有一个对象
s2在内存中有两个对象
System.out.println(s1==s2);//true 指向的是同一个对象
System.out.println(s1==s3); //false 不是一个对象 内存地址不同
System.out.println(s1.equals(s3));//true 不是一个对象,但内容相同
}
}
字符串常用操作方法:
1.字符串去头尾空格:
2.字符串反转(或部分反转):
3.字串个数
4.获取最大相同字串
StringBuffer:
StringBuilder:单线程使用StringBuilder可提高效率
28.包装类
自动装箱拆箱:
29.集合
集合用来存储对象(内存地址),长度是可变的,可以存储不同类型对象。
Collection---List Set
List--- ArrayList LinkedList Vector
Set--- HashSet TreeSet
Iterator接口 迭代器:
30.List集合
元素是有序的,可以重复,集合有角标。
ArrayList:
LinkedList:
封装LinkedList到自己的类中:
队列/堆栈
Vector:
31.Set集合
元素无序(存入和取出的顺序不一定一致),且元素不能重复。
HashSet:
底层数据结构为哈希表,集合中元素的存的顺序为元素哈希值大小顺序。如果哈希值相同,在判断是否是同一对象(equals()),不是则顺延存入。
例子:
TreeSet
可以对Set集合中的元素进行排序,按ASCII顺序。 底层数据结构为二叉树
(1)对象类中自带比较
存对象时,需要实现Comparable接口中的唯一一个函数 compareTo()方法,并定义返回的int型,
返回负数,零,正数 分别代表 晓宇 等于 大于。等于时对象相等,不存入。主要条件相同时,判断次要条件。
如果想要实现按存入顺序取出的话,compareTo方法就一直返回正数。取出时默认从小到大取。
(2)TreeSet构造函数中传入比较器
定义自定义比较器类实现Comparator
对象和容器都有排序时,以容器的排序为主。
32.集合泛型
集合中使用泛型来约束集合中要存储的类类型
当类中要操作的引用数据类型不确定的时候,定义泛型类。(静态方法不能访问类上定义的类型,因为静态方法存在时,类还不存在)
方法也可以定义泛型。泛型放在返回值前面
接口也可以定义泛型
泛型限定
33.Map
HashMap
keySet:取出所有键存到Set中
entrySet:取出每一条数据映射关系存到Set中
34.Collections
工具类 里面的方法都是静态的 不需要创建对象
sort(List) 给list排序
sort(List,comparator<T>) 给list排序,并且自己定义比较器
binarySearch 二分法查询 不存在返回负插入点-1
fill(List,Object) 替换集合中的所有元素为Object
replaceAll(List , old, new) 将List中所有的old替换成new
reverse(List) 反转
reverseOrder(Comparator c) 返回一个逆转指定Comparator比较器顺序的比较器
synchronizedCollention(Collection c) 返回一个线程安全的集合
shuffle(List) 将List中的数据按随机源进行置换
35.Arrays
用于操作数组的工具类,里面全是静态类
asList() 将数组变成List集合,但是不可以使用集合的增删方法,因为数组的长度是固定的,如果增删了,会发生 UnsupportedOperationException异常 。如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集 合中的元素,如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。(int数组与Integer 数组)
可以使用集合的方法来操作数组,例如 contains,get,indexOf,subList等。
集合也可转成数组-->list.toArray(new 数组类型[?])
?为数组长度,当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组,长度为集合的size。当指定类型的数组长度大于了集合的size,就不会新创建数组,而是使用传递进来的数组,所以创建一个刚刚好的数组最优。
集合变数组是为了限定对元素的操作。不可增删。
sort() 排序 也可局部排序
binarySearch() 二分查询
fill() 替换 也可范围替换
toString() 将数组中元素按字符串形式输出
37.增强for循环
for(数据类型 变量名:被遍历的Collection(List Set)和数组 )
{
}
对集合进行遍历,但是不能对集合进行操作。
迭代器(Iterator)除了遍历,还可以remove集合中元素的动作。
如果是用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
传统for循环和增强for循环有什么区别?
增强for循环有一个局限性,必须有被遍历的目标。
建议在遍历数组的时候,还是希望用传统for,因为传统for可以定义角标。
38.可变参数
其实就是数组参数的简写形式,不用每一次都手动的建立数组对象,只要将要操作的元素作为参数传递即可,隐式的将这些参数封装成了数组。
在定义时,可变参数一定要定义在参数列表最后。
39.静态导入
StaticImport 静态导入
import static java.util.Arrays.*;导入这个类中所有静态成员----->数组工具类
import static java.lang.system.*;导入这个类中所有静态成员---->syso
当类名重名时,需要指定具体的包名。
当方法重名时,要指定具体的所属的对象或类。
40.System
描述系统的一些信息 类中的属性和方法都是静态的
out 标准输出 默认是控制台
in 标准输入 默认是键盘
arraycopy() 复制一个数组
exit() 终止虚拟机
currentTimeMillis() 获取当前时间的毫秒值
gc() 运行垃圾回收器
getProperties() 获取系统属性信息
可以自己将一些信息存到table中,随时取
41.Runtime
每一个JAVA应用程序都有一个Runtime类的实例,使应用程序能够与其运行的环境相连接。
没有构造函数,不能创建对象,类中函数全部都是非静态的。
所以它提供了一个函数来返回一个Runtime对象 getRuntime();----->单例设计
exec(String command) 在单独的进程中执行指定的字符串命令
Process 全是抽象方法,并且没有子类,由底层方法实现。
destroy() 杀掉子进程。
42.Date
表示特定的时间 精准到毫秒
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date));//2018-07-12 19:49:10
System.out.println(date.getTime());//1531396150467
System.out.println(date);//Thu Jul 12 19:49:10 CST 2018
43.Calendar
时间所有信息 单例 getInstance
DAY_OF_YEAR ...
c.get(Calendar.YEAR)
44.Math Random
abs() 返回绝对值
ceil() 返回大于这个参数的最小整数
floor() 返回晓宇这个参数的最大整数
round() 四舍五入
pow(T x,T y) x的y次幂
random() 返回 [0,1)的double伪随机数
45.IO流
字节流处理多媒体文件
字符流处理文本文件
流操作的基本规律
流对象很多,不知道该用哪一个
通过三个明确来完成
1,明确源和目的
源:输入流 InputStream Reader
目的:输出流 OutptStream Writer
2,操作的数据是否是纯文本
是:字符流
不是:字节流
3,当体系明确后,再明确要使用哪个具体的对象
通过设备来进行区分
源:内存,硬盘,键盘
目的:内存,硬盘,控制台
字节流的抽象基类:InputStream OutputStream
字符流的抽象基类:Reader Writer
由这四个基类派生的子类都是以他们的名字作为后缀名。FileInputStream FileReader
IO异常处理方式
FileWriter fw=null;
try {
fw=new FileWriter("demo.txt",true);//相对路径 创建对象 boolean append
fw.write("hahaha");//将字符串写入到流中
fw.flush();//刷新流对象中的缓冲中的数据 将字符串刷新到目的地中
} catch (IOException e) {
System.out.println(e.getMessage());
}
finally
{
try {
if(fw!=null)//判断fw是否初始化成功
fw.close();//关闭流资源,在关闭前先刷新流对象
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
字符流----->Reader Writer
FileWriter 该对象一被初始化,就必须要有被操作的文件
windows换行:\r\n Linux换行:\n
FileWriter fw=new FileWriter("demo.txt",true);//相对路径 创建对象 boolean append
fw.write("hahaha");//将字符串写入到流中
fw.flush();//刷新流对象中的缓冲中的数据 将字符串刷新到目的地中
fw.close();//关闭流资源,在关闭前先刷新流对象
FileReader
//读取单个字符
FileReader fr=new FileReader("demo.txt");
try {
fr=new FileReader("demo.txt");
int c=fr.read();
while(c!=-1)
{
System.out.print((char)c);
c=fr.read();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
finally
{
try {
if(fr!=null)//判断fw是否初始化成功
fr.close();//关闭该流并释放与之关联的所有资源。
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
//通过字符数组进行读取
FileReader fr=new FileReader("demo.txt");
try {
fr=new FileReader("demo.txt");
int num=0;
char[] buf=new char[1024];
while((num=fr.read(buf))!=-1)
{
System.out.print(new String(buf,0,num));
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
finally
{
try {
if(fr!=null)//判断fw是否初始化成功
fr.close();//关闭流资源,在关闭前先刷新流对象
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
复制文件
FileWriter fw=null;
FileReader fr=null;
try {
fw=new FileWriter("demo_copy.txt",true);
fr=new FileReader("demo.txt");
int len=0;
char[] buf=new char[1024];
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);
}
} catch (IOException e) {
throw new RuntimeException("读写失败");
}
finally
{
if(fr!=null)
try {
fr.close();
} catch (IOException e2) {
System.out.println(e2.toString());
}
if(fw!=null)
try {
fw.close();//关闭流资源,在关闭前先刷新流对象
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
缓冲区的出现提高了对数据的读写效率,缓冲区要结合流才可以使用
BufferedWriter BufferedReader
该缓冲区提供了一个换行方法 newLine();
该缓冲区提供了一次读一行的方法 方便对文本数据的读取 readLine(); 返回null时 表示读到末尾 方法返回时只返回回车符之前的内容。不返回回车符。最终都是在硬盘上一个一个读取,所以最终还是使用read方法一次读一个的方法。其实readLine中有一个数组(长度足够),用read方法读取字符后存到数组中。
FileWriter fw=null;
FileReader fr=null;
BufferedWriter bw=null;
BufferedReader br=null;
try {
fw=new FileWriter("buf_copy.txt");
bw=new BufferedWriter(fw);
fr=new FileReader("buf.txt");
br=new BufferedReader(fr);
String str=null;
while((str=br.readLine())!=null)
{
bw.write(str);
bw.newLine();
}
} catch (Exception e) {
}
finally {
if(bw!=null)
try {
bw.close(); //关闭缓冲区,就是在关闭缓冲区中的流对象
} catch (IOException e2) {
System.out.println(e2.toString());
}
if(br!=null)
try {
br.close(); //关闭缓冲区,就是在关闭缓冲区中的流对象
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
LineNumberReader
可获取行号 getLineNumber()
字节流------> InputStream OutputStream
FileOutputStream FileInputStream
字节流在不需要缓冲的情况下 不需要刷新
字节流复制文件
FileInputStream fis=null;
FileOutputStream fos=null;
try {
fis=new FileInputStream("buf.txt");
fos=new FileOutputStream("buf_copy2.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
/* 这种方法是给数组分配正好空间的 只适用于操作小文件
int length=fis.available();//获取文件中的大概字节数
byte[] buf=new byte[length];
fis.read(buf);
fos.write(buf);
*/
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
if(fis!=null)
try {
fis.close();
} catch (IOException e2) {
System.out.println(e2.toString());
}
if(fos!=null)
try {
fos.close();
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
字节缓冲区流复制文件
FileInputStream fis=null;
FileOutputStream fos=null;
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
fis=new FileInputStream("buf.txt");
fos=new FileOutputStream("buf_copy2.txt",true);
bis=new BufferedInputStream(fis);
bos=new BufferedOutputStream(fos);
int byt=0;
byte[] bytt=new byte[1024];
while((byt=bis.read(bytt))!=-1)
{
bos.write(bytt,0,byt);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
if(bis!=null)
try {
bis.close();
} catch (IOException e2) {
System.out.println(e2.toString());
}
if(bos!=null)
try {
bos.close();
} catch (IOException e2) {
System.out.println(e2.toString());
}
}
转换流
FileReader 默认编码表GBK
InputStreamReader 可指定编码表 要传入字节流 FileReader为该类的子类
//键盘录入
//获取键盘录入对象
InputStream in=System.in;
//将字节流对象转成字符流对象,使用转换流
InputStreamReader isr=new InputStreamReader(in);
//使用缓冲区高效操作字符串
BufferedReader br=new BufferedReader(isr);
OutputStream out=System.out;
OutputStreamWriter osw=new OutputStreamWriter(out);
BufferedWriter bw=new BufferedWriter(osw);
String str=null;
while((str=br.readLine())!=null)
{
if("over".equals(str))
break;
bw.write(str);
bw.newLine();
bw.flush();
}
46.异常的日志信息
System.setIn() 重新指定输入流
System.setOut() 重新指定输出流
异常的日志信息
//log4j
try{
int[] arr=new int[3];
System.out.println(arr[3]);
}
catch(Exception e)
{
try{
Date d=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date=sdf.format(d);
PrintStream ps=new PrintStream("Exception.log");
ps.println(date);
System.setOut(ps);
}
catch(IOException e2)
{
throw new RuntimeException("日志文件创建失败");
}
e.printStackTrace(System.out);
//e.printStackTrace(new PrintStream("Exception.log"));
}