老杜,永远的神!!!
java 语言的特性
1、简单性。java中支持单继承,c++中支持多继承,Java中没有指针。
2、健壮性。java中有自动垃圾回收机制(GC),jvm会将堆区没有变量指向的对象回收。
3、可移植。一次编译,到处运行。我们只需要编译成字节码文件,然后在windows或linux系统安装jre运行环境即可。
4、多线程。处理程序更加快速。
5、安全性。开源的代码对程序员来说更加安全。
JDK 安装
下载好JDK(java开发工具箱)后,如果需要在DOS命令行下编译运行java文件,则需要提前为系统配置环境变量,将JDK下的bin目录添加到环境变量中的PATH路径下。
JDK中包含jre和jvm,jre中包含jvm。如果需要开发的话,则需要安装JDK,如果只需要运行字节码文件,则只需要安装对应系统的jre即可。此处体现了java文件的可移植性。
java 的编译运行
在DOS窗口下,javac+源文件路径 进行编译
java +类名 进行运行
java 程序的运行过程
使用javac进行编译,生成字节码文件,然后使用java命令调用JVM虚拟机,此时JVM会启动类加载器(classloader),在当前目录下对字节码文件进行查找,找到后类加载器会将此文件交给JVM,JVM将其翻译为操作系统能读懂的二进制文件。(从JDK11之后包括JDK11,可以直接使用 java + 源文件路径进行编译运行)
java 中的标识符
在java中,标识符是我们可以修改的东西,并非系统定义。
是标识符的有:
类名、方法名、变量名、常量名、接口名。
标识符可以有:
数字、字母、下划线、美元符号组成,但是数字不能当作开头。
严格区分大小写,长度不做限制。
标识符的命名规范:
类名、接口名:遵循驼峰命名法(每个单词的首字母大写,其余小写)。
变量名、方法名:第一个单词全部小写,后面每个单词遵循驼峰命名法。
常量名:每个单词全部大写,中间用下划线分割。
变量
内存中最基本的存储单元,是用来盛放数据的盒子。
变量三要素:
数据类型、变量名、字面量(数据)。
int i = 1;
变量的位置不同,会导致变量的作用域不同。大作用域包纳小作用域,比如成员变量作用域包括局部变量作用域。
数据类型
java中数据类型整体分为两种。基本数据类型和引用数据类型。基本数据类型是系统默认的,引用数据类型除了String,其他的基本都是用户自己定义。
基本数据类型:
byte(1) 、short(2)、int(4)、long(8) 、float(4)、double(8)、boolean(1)、char(2)
引用数据类型:
String(String是由多个char字符拼接而成)。。。。
其中:
byte 表示范围:-128~127
short 表示范围:-32768~32767
int 表示范围:-2147483648~2147483647
char 表示范围:0~65535
在取值范围内可以直接赋值。除了boolean类型,其他基本类型数据可以相互转换。
小容量直接可以转换成大容量,被称为:自动类型转换。
大容量可以转换成小容量,被称为:强制类型转换。(会损失精度)
int i =(int)2147483648; //超出Int范围强转成Int
输入整型数据会被默认当作int类型处理,输入浮点数据会被当做double类型处理。
float i = 5.0F;
long j = 2147483648L; //不加L会被当做int类型数据,int赋值给long会报错
当byte、short、int、char混合运算时,jvm会将其先转换成int类型然后进行转换
字符编码
最初的计算机编码是ASCII码,虽然采用一个字节来存储ASCII码,但是第一位被舍弃掉了,因此ASCII码最多只有128个。
后来出现了ISO-8859-1编码,也就是我们常用的latin-1,但是此种编码并不支持中文,再后来出现了GB2312、GBK、GB18030(三种字符编码容量大小从左到右依次增大)这些编码,中文才被支持。
java中使用Unicode编码(为了支持全球文字),这种编码支持utf8、utf16、utf32······
繁体中文的字符编码是big5。
逻辑运算符
算术运算符: 关系运算符: 逻辑运算符: 三目运算符:
±*/%+±- > >= < <= == != &|! && || 布尔表达式?表达式1:表达式2
接收键盘输入数据
java.util.Scanner s = new java.util.Scanner(System.in);
int i = s.nextInt();//接收整数
String j = s.next();//接收字符串
循环、转向、分支语句
循环包括 for、while、do while 三种,转向包括break、continue、return,选择包括if和switch
for(i=0;i<10;i++){
if(i==5){
continue;//当i等于5时跳出本次循环,进行下一次循环
system.out.println("这是第"+i+"次打印");
}
if(i==9){
system.out.println("这是第"+i+"次打印");
break;//当i等于9时终止本层循环
}
}
switch(值){//String、int类型都可以
case 值1:
java语句;
break;//如果不写break会出现case击穿,一直击穿到找到break或者找到分支最后才能结束
case 值2:
java语句;
break;
············
default
java语句;
}
do…while循环必定会被执行一次,break终止本层循环,continue跳出本次循环,进行下一次循环,return 用来返回结果和终止当前方法。
方法
可以重复使用,并具有特定功能的代码片段。
定义:
[修饰符列表] 返回值类型 方法名(形式参数列表){方法体;}
方法体中的语句自上至下顺序执行。
当方法和入口在同一个类中,可以直接调用方法。如果不在同一个类,则需要输入‘’类名.方法‘’调用。
当我们使用递归方法时务必保证最后要有终止条件,否则递归一直调用,会产生栈内存溢出错误。
jvm内存空间
JVM内存空间主要有三部分,堆区、栈、方法区。
方法区:
JVM启动后,会调用类加载器将字节码文件加载到方法区,形成代码片段,此时的方法区存放的都是未运行字节码文件。此外,在类加载时也会将我们的静态变量赋值,并且也会执行我们的静态代码块。
栈区:
当我们调用方法时,JVM会将对应的方法加载到栈区,这个过程叫做压栈。如果正在执行的代码片段(方法)运行中又调用了方法, JVM则会继续压栈,直到调用最后一个方法后,并且执行完成后,开始从最后一个方法,依次反向执行该方法前一个方法代码,直到结束,这个过程叫做弹栈。因此,栈区存放了正在运行的方法和局部变量。(栈区的栈帧永远指向栈顶,因此处于栈顶的方法为活跃状态)
堆区:
当我们堆区的方法正在执行的过程中,可能会创建对象,我们创建的这个对象,就会被JVM放到堆区,创建好对象后,JVM会为我们返回一个内存地址,该内存地址即为创建对象的地址,这个地址会返回给我们的引用。如果我们的引用没有被释放则我们对应在堆区的对象也不会被释放。因此,堆区存放了我们创建的对象,和对象中的实例变量。
User u = new User();//其中new User();是对象,u是引用,u前面的User是该变量的数据类型。
面向对象
对于C语言来说,是完全面向对象的。面向对象虽然在编写代码时舒服,但是代码与代码之间耦合度高,一环套一环,后期拓展起来非常麻烦。C++是一种半面对对象半面对过程的代码,我们的JVM底层代码就是使用C++实现的。
对于java语言,是完全面向对象的。在我们现实生活中,所接触的都是面对对象,比如:小明打篮球。小明是一个对象,篮球也是一个对象,而他们之间有一个打的行为。采用面向对象编程开发,可以更加方便我们编程。
面向对象术语:
OOA 面向对象分析
OOD 面向对象设计
OOP 面向对象编程
面向对象三大特征:
封装、继承和多态。
封装——>继承——>多态(层层相扣)
类和对象
类:现实世界中的某些事物具有相同特征,我们将这些特征抽象成一个概念,这个概念就叫做类。
对象:现实中存在的个体。(实例是对象的另一种称谓)
将对象的特征抽取成类的过程称为抽象。用类创建对象的过程称为实例化。
类里面的变量被称为实例变量,方法被称为实例方法(也可以有其他类型的方法),实例方法不能被static修饰,实例方法的调用需要‘’引用.方法名‘’调用。类中的变量实际就是属性,方法就是动作。 A has a B 说明A有一个属性是B。
对象是我们new出来的,在上面已经描述过对象的创建方法,在此不再赘述。
构造方法
当我们创建好一个类后,如果我们不为其创建构造方法,则系统会默认生成一个无参构造方法(也被称为缺省构造器),但是如果我们创建了一个有参构造方法,则系统不会再为我们创建无参构造方法,因此我们需要将有参和无参构造方法都创建好。
class User(){
int id;
String name;
public User(){}//无参构造
public User(int id,String name){
this.id = id;//this指代的是当前对象的地址
this.name = name;
}
}
封装
我们类中属性一般都推荐使用private修饰符进行修饰,被private修饰的变量只能在本类中进行使用,在别的类则需要使用get()和set()方法进行调用,我们可以在get()和set()方法中对程序调用变量和赋值进行限制。使用封装可以让我们更好的保护类的内部。我们只需要调用入口就可以访问了。
static关键字修饰的变量我们可以通过‘’类名.变量名‘’或是‘’引用.变量名进行访问‘’,但是静态变量我们推荐前者的调用形式,静态变量一般赋初值后就不会再改变,因此我们不需要每次new对象时都进行赋值(在类加载时就已经被赋初值,存放在方法区)。
class User{
private int id;
public User(){}//无参构造
public User(int id){
this.id = id;//this指代的是当前对象的地址
}
public int getId(){
return this.id;
}
public void setId(int id){
if(id==123)
return ;
this.id=id;//说明只有输入的id不是123时才能成功更改id值
}
}
class Test{
public static void main(String[] args){
User u = new User();//创建User对象,调用无参构造
u.setId(456);
System.out.println(u.getId());//打印id
}
}
this关键字只在本类中使用,大部分情况下可以省略,但是如果传进来的形参和要赋值的形参名字相同则不能省略,比如this.name=name就不可以省略,否则就是自己给自己赋值。this()放在构造方法的第一行时,代表可以调用有参构造进行传值。
class User{
int id;
public User(){
this(678);//在无参构造方法中调用有参构造,进行赋值
}//无参构造
public User(int id){
this.id = id;//this指代的是当前对象的地址
}
}
继承
子类继承父类的属性和方法(构造方法不能被继承,由父类继承下来的私有属性不能直接访问,但是可以使用get和set方法),实现代码复用。继承也为后来的多态打下了基础,有了继承才会有多态。在java语言中,类只支持单继承,所有的类最终都会继承Object类,这个类是所有类的祖先。凡是能被A is a B 描述的,则A是子类,B是父类。
class Anmial{
private String name;
public void move(){//move方法
System.out.println("动物在行走");
}
public Anmial(){}
public Anmial(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
}
class Cat extends Anmial{
public void move(){//重写父类的move()方法
System.out.println(this.getName()+"在行走");
}
}
class Test{
public static void main(String[] args){
Cat cat = new Cat();
cat.setName("猫");//使用setName方法为继承父类的私有属性赋值
cat.move();
}
}
重写(覆盖)
当我们继承父类方法后如果父类方法不能满足我们的需求,则需要我们对父类方法进行重写。重写是为了拓展需求。
注意:
由于构造方法不能被继承,因此不能被重写。
重写后的方法权限要比父类的高,抛出的异常要比父类的少。
重写的是方法,不是属性,私有方法不能被重写,静态方法重写没有意义。
重写时要求返回值类型,方法名,参数列表都相同,方法体不同。(方法重载要求变量值相同,形式参数不同(形参个数、顺序、类型),方法体类似,方法重载是为了解决多个方法拥有相似的功能。)
多态
编译和运行时有两种形态:编译时静态绑定,运行时动态绑定。
多态的使用
class Anmial{
private String name;
public void move(){//move方法
System.out.println("动物在行走");
}
public Anmial(){}
public Anmial(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
}
class Cat extends Anmial{
public void move(){//重写父类的move()方法
System.out.println(this.getName()+"在行走");
}
}
class Test{
public static void main(String[] args){
Anmial cat = new Cat();
if(cat instanceof Cat){
Cat x = (Cat)cat;
x.setName("猫");//使用setName方法为继承父类的私有属性赋值
x.move();
}
}
}
在Test类中, 26行Anmial cat = new Cat(),Anmial 是父类型,引用指向了子类型Cat。在编译时从左边编译,则认为变量cat为Anmial类型,则28行在编译时检测到的是父类的move方法;但是在运行时从右往左运行,则会调用子类重写后的move方法,实现多态的应用。
Anmial cat = new Cat(),这种new对象方法被称为向上转型,Cat是子类,被转成了父类Anmial类型。
Cat x = (Cat)cat,这种被称作向下转型,由于向下转型时可能会产生错误(如果cat引用指向的对象地址并不是Cat类型),因此需要在向下转型时用 “A instanceof 下转的类型“进行判断,类型一致才可以强转。
注意:
使用多态的前提:必须有继承关系。
super
super()方法可以主动调用父类有参或是无参构造方法(如果不传参数,默认调用无参构造方法),如果没有父类,默认继承Oject类(因此,如果有多层继承的话,调用super()方法时,也会一层一层往上寻找父类构造方法,并按栈的定义顺序执行)。
只要有无参构造方法就会有super()方法,当我们创建无参构造方法时,如果内部什么也不填写,则默认为其添加super()方法。
super()方法只能放在构造方法的第一行,this()方法也只能放在构造方法的第一行,因此两种方法在一个构造方法只能存在一种。
super也代表父类的地址,因此可以用”super.“来调用父类方法或者属性(只能用于实例方法)。
抽象类
抽象类:是由两个或多个已经经过抽象的类,再次进行抽象形成的类。比如:储蓄卡类、信用卡类可以进一步抽象成银行卡类。
抽象(abstract)类中可以有抽象方法(抽象方法没有方法体),也可以有非抽象方法,但是如果一个类有抽象方法,那么他一定是抽象类。
抽象类虽然不能被实例化(类抽象出来的类不可以实例化),但是可以被继承,但是如果子类继承了父类的抽象方法,则一定要重写父类中的抽象方法。
abstract class Card{
private int id;
abstract void printId();//抽象方法没有方法体
public Card(int id) {
this.id = id;
}
public Card() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Creditcard extends Card{
public void printId(){
System.out.println("信用卡ID是"+this.getId());
}
}
class Depositcard extends Card{
public void printId() {
System.out.println("储蓄卡ID是" + this.getId());
}
}
class Test{
public static void main(String[] args) {
Card c1 = new Creditcard();
c1.setId(100001);
Card c2 = new Depositcard();
c2.setId(200001);
c1.printId();
c2.printId();
}
}
final
被final修饰的类不能被继承,修饰的方法不能被重写,修饰的变量只能赋值一次。
final和static合用创建的被称为常量,如果我们不为其赋初值,则在new对象的时候,系统调用无参构造方法,会自动为其赋初值,但是一旦赋值后,则无法修改,这个常量也就失去了意义,因此需要我们在程序为它赋初值之前就为其赋值。我们可以在有参构造方法中为其传值,也可以直接在常量名后直接赋值。(常量命名规范:所有字母全部大写,每个单词之间用下划线连接)
由于final修饰的类不能被继承,而抽象类一般都是用来继承的,因此,final和抽象类(abstract)不能连用。
接口
接口中只能存放常量和抽象方法,因此我们可以将常量的public static final 和抽象方法的 public abstract省略,程序在调用时会自动补上。
接口与类不同,类只支持单继承,而接口支持多继承,当我们创建好类及其内部的方法后,一定要为其常量赋值,为接口的抽象方法进行实现(implements),若未实现抽象方法,程序则无法正常运行。(一个类可以实现多个接口)
接口的应用是为了减轻调用者和实现者的耦合度,接口就像一个中间人,调用者只需要关心接口有什么功能可用,不需要关心该功能如何实现。把他们两个分开了。
类所拥有的属性可以被接口实现。
--------------------------------------------------------------------------创建接口
interface CardNature{//用接口来设置卡片有什么内容
void printColour();
void printSize();
}
---------------------------------------------------------------------------实现接口方法
class Card implements CardNature{//用来实现卡片接口的方法
private String cardColour;
private String cardSize;
public Card(String cardColour, String cardSize) {
this.cardColour = cardColour;
this.cardSize = cardSize;
}
public Card() {
}
public String getCardColour() {
return cardColour;
}
public void setCardColour(String cardColour) {
this.cardColour = cardColour;
}
public String getCardSize() {
return cardSize;
}
public void setCardSize(String cardSize) {
this.cardSize = cardSize;
}
public void printColour(){
System.out.println("卡片的颜色是"+this.getCardColour());
}
public void printSize(){
System.out.println("卡片尺寸是"+this.getCardSize());
}
}
-------------------------------------------------------------------------创建调用者
class User{//用户只看到接口的东西,不关心如何实现
CardNature cardNature;
public User(CardNature cardNature) {
this.cardNature = cardNature;
}
public CardNature getCardNature() {
return cardNature;
}
public void setCardNature(CardNature cardNature) {
this.cardNature = cardNature;
}
public void seeColour(){
cardNature.printColour();
}
public void seeSize(){
cardNature.printSize();
}
}
---------------------------------------------------------------------------测试
class Test{
public static void main(String[] args) {
CardNature c = new Card();//向上转型
if(c instanceof Card){//判断c是否为Card类型,是的话就向下转型
Card cc = (Card)c;//向下转型
cc.setCardColour("白色");//设置属性
cc.setCardSize("10cm");
User u =new User(cc);
u.seeColour();//用户看卡片颜色
u.seeSize();//用户看卡片尺寸
}
}
}
数组
数组是在一段连续的空间内存放多个相同数据类型数据的存储方式,存放的数据类型可以是基本数据类型,也可以是引用数据类型,如果存储的是基本数据类型,则数组空间里存放的都是数据,如果是引用数据类型,则存放的都是对象的地址,我们可以通过地址来找到这个对象。
由于数组的存储空间是连续的,因此我们创建的数组时,程序返回给数组的引用是这个数组的初始地址。由于我们已经知道该地址存放何种数据类型,因此我们可以通过首地址和数组中的值的下标来确定这个值的地址(初始地址+(下标+1)*数据类型长度)。
数组的初始化有两种形式,一种是静态初始化,一种是动态初始化。数组的遍历只需要使用for循环即可。
public class Test{
public static void main(String[] args) {
int[] arr1 = new int[]{1,4,6,4,3};//静态初始化
int[] arr2 = new int[5];//动态初始化
for (int i=0;i<5;i++){
arr2[i]=(int)(Math.random()*10);
}
String[] arr3 = new String[5];//引用数据类型数组
arr3[0]="小明";
arr3[1]="小花";
arr3[2]="小黄";
for (int i=0;i<5;i++)
System.out.println(arr1[i]);
System.out.println("---------------------------------");
for (int i=0;i<5;i++)
System.out.println(arr2[i]);
System.out.println("---------------------------------");
for (int i=0;i<5;i++)
System.out.println(arr3[i]);
}
}
数组的拷贝,可以调用库中的System.arraycopy()方法。(System.arraycopy(拷贝源,开始位置,拷贝目的地,开始位置,长度))。数组有一个缺点,一旦确定长度,则不能改变,因此,当我们的数组长度不能满足我们的需求时,我们需要new一个更大容量的数组,使用System.arraycopy()方法,将原数组中的值拷贝到新数组,原数组可以释放。
public class Test{
public static void main(String[] args) {
int[] arr1 = new int[]{1, 4, 6, 4, 3};
int[] arr2 = new int[10];
System.arraycopy(arr1, 0, arr2, 0, arr1.length);
for (int i=0;i<5;i++)
System.out.println(arr1[i]);
System.out.println("--------------------------------------");
for (int i=0;i<10;i++)
System.out.println(arr2[i]);
arr1=null;
}
}
数组中的System.arraysort
访问控制权限
访问控制权限 | 本类 | 同包 | 子类 | 跨包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
默认(什么也不加) | √ | √ | ||
private | √ |
访问控制权限修饰符可以修饰什么?
属性(4个都能使用)
方法(4个都能使用)
类(public和默认可以用)
接口(public和默认可以用)
String、StringBuffer、StringBuilder
String数据类型属于引用数据类型,但是在使用过程中我们不需要new对象,可以直接为其赋值。
被双引号引起来的字符内容是不可变的。(底层存放字符的byte数组被final修饰,一旦放入字符串常量池,则无法修改值)
字符串常量池:
位于方法区,里面存放了我们创建的字符串,每创建一个字符串就会生成一个字符串对象,并且无法修改,如果是两个不同的字符串相加,则会生成三个字符串。这些字符串都有自己独特的存储地址。
String str1 = "123";//创建第一个字符串对象
String str2 = "456";//创建第二个字符串对象
String str = str1+str2//创建第三个字符串对象
String str3 = "123";//不会创建新的字符串,因为"123"字符串已经在第1行被创建过了
String str4 = "123";
String strr = str3+str4//创建第四个字符串对象
整数型常量池:
由于-128~127这些数据,经常会被系统调用,因此JVM在方法区开辟了一块空间专门存放这些常用整数,叫做整数型常量区。方便我们 调用。这些数据都有自己独特的地址。
如果我们频繁的对字符串进行拼接,则程序会不停的在静态常量池生成字符串对象,降低效率,而StringBuffer则可解决这种问题,StringBuffer底层调用了Byte数组,这个数组默认长度为16,我们可以使用System.append()方法往数组中添加字符,这些字符并不会放入字符串常量池,当我们存放满Byte数组后,底层会自动调用Arrayscopy()方法对数组进行扩容。
StringBuffer和Stringbuilder区别:
StringBuffer在多线程运行环境下是安全的。
StringBuilder在多线程运行环境下是不安全的。但是在单线程中它的效率是比StringBuffer高的。
String方法
import java.util.Locale;
public class StringTest {
public static void main(String[] args) {
String str = new String(" QWEdw#21#23 ");
System.out.println(str);//打印字符串到控制台
System.out.println("字符串第二位:"+str.charAt(2));
System.out.println("2第一次出现的位置:"+str.indexOf("2"));
System.out.println("2最后一次出现的位置"+str.lastIndexOf("2"));
System.out.println("字母全部转换成小写:"+str.toLowerCase());
System.out.println("字母全部转换成大写:"+str.toUpperCase());
System.out.println("截取第二位之后的字符串:"+str.substring(2));
System.out.println("截取2到6的字符串:"+str.substring(2,6));
System.out.println("判断字符串是否为空:"+str.isEmpty());
System.out.println("除去前后空白:"+str.trim());
System.out.println("将“2”替换成“1”:"+str.replace("2","1"));
System.out.println("判断字符串中是否有“dw”字符串:"+str.contains("dw"));
System.out.println("获取字符串长度:"+str.length());
System.out.println("判断字符串是否以“QW”开头:"+str.startsWith(" QW"));
System.out.println("判断字符串是否以“23”结束:"+str.endsWith("23 "));
System.out.println("判断字符串是否包含“dw”字符串:"+str.contains("dw"));
System.out.println("以”#“分割字符串");
String[] s = str.split("#");
for(int i=0;i<s.length;i++)
System.out.println(s[i]);
System.out.println("将字符串转换成char数组");
char[] c = str.toCharArray();
for(int i=0;i<c.length;i++)
System.out.print(c[i]+" ");
System.out.println();
System.out.println("-----------------------------------------------------");
String str1 = new String("aSdAB123");
String str2 = new String("asDAB123");
System.out.println(str1);
System.out.println(str2);
System.out.println("比较两个字符串是否相等:"+str1.equals(str2));
System.out.println("比较两个字符串是否相等,不区分大小写:"+str1.equalsIgnoreCase(str2));
}
}
异常处理
Throwable是一个类,它的子类是Error和Exception(Exception类下有一个RunTimeException(运行时异常)类和编译时异常类),其中Error类一旦发生,JVM会直接退出,无法挽回。而Exception类是我们可以处理的。
Exception异常分为:编译时异常、运行时异常(RunTimeException)。其中编译时异常是我们必须要处理的,如果我们不处理,则代码无法被编译器通过,而运行时异常,我们可以处理,也可以不处理。无论是编译时异常或是运行时异常都发生在运行期。
我们处理编译时异常时一般有两种方式,一种是“向上抛”(throws),另外一种是直接在当前处理(try…catch)。
我们在方法入口出不能向上抛,必须处理。我们不能把异常交给JVM处理。
如果使用throws抛异常的话,则发生异常后的代码片段都不会执行,而如果使用try…catch处理异常,则try…catch之后的代码片段依然会执行。
自定义异常类
public class IllegalInputException extends Exception{//手动创建异常类
public IllegalInputException(){}
public IllegalInputException(String s){
super(s);
}
}
测试异常
import java.util.Scanner;
//用户输入name和密码,判断name是否合法(6-14位),如果不合法,报出我们定义的异常
public class RegisterTest {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("请输入用户名:");
String s1 = s.next();
System.out.println("请输入密码:");
String s2 = s.next();
UserService u =new UserService();
try {
u.register(s1,s2);
} catch (IllegalInputException e) {
e.getMessage();
System.out.println(e);//打印异常信息
}
}
}
class UserService { //定义类
private String username;
private String password;
public void register(String username,String password) throws IllegalInputException {
if(username.length()>14||username.length()<6)
throw new IllegalInputException("输入数值不合法");//手动抛出异常,上面已经定义过这个异常,并输入异常信息
this.username=username;
this.password=password;
}
}
在try不仅可以和catch搭配,还可以和finally搭配(try…catch、try…finally、try…catch…finally)。放在finally里的代码片段一定会执行。
try中返回了结果,finally中的语句块仍会正常执行,JVM会将i中的值找一个变量先存起来,此时然后继续执行finally中的语句快。最后返回第一个执行的return语句,此时返回的不是i而是存值的那个变量。(java语言,自上至下执行)
public class TryTest { //结果是1
public static void main(String[] args) {
int i=1;
System.out.println(question(i));
}
public static int question(int i){
try{
return i;
}
finally {
return ++i;
}
}
}
集合
集合主要有三类:list、map、set。
集合只能放对象的地址。
Map可以转Set(使用entrySet或keySet,keySet需要在遍历时获取value值),Set可以转List(new集合时把Set集合放进去),但是反过来转会有问题。
集合的遍历一般使用迭代器、增强for循环。
Iterable(接口)的子类是Collection(接口),Collection的子类Set(接口)和List(接口)
List(有序,可重复)下有:ArrayList、LinkedList、Vector等常用类。
Set(无序,不可重复)下有:HashSet、TreeSet等常用类。(TreeSet的父类是SortedSet)。
Map(无序,不可重复)下有:HashMap、Hashtable、properties(是Hashtable的子类)、TreeMap(父类是SortedMap)等常用类。
ArrayList:
ArrayList集合底层是一个Object[]数组,并且这个Object[]数组长度初始化为10,并且这个容量是在赋第一个值时,JVM赋给Object[]数组的。当Object数组容量满后,此数组会以1.5倍的关系扩容。因为ArrayList底层是由数组实现,因此,查询和修改效率较高。但是ArrayList是非线程安全的。
mport java.util.*;
public class CollectionTest {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("123");
c.add(100);
String s = new String("SAD");
c.add(s);
System.out.println(c.size());
c.remove(100);
System.out.println(c.size());
Iterator it =c.iterator();
while (it.hasNext())//使用迭代器遍历集合
System.out.println(it.next());
LinkedList:
LinkedList集合底层是一个双向链表,链表没有初始长度,可以一直扩容。并且链表的增删效率较高。
List<String> l =new LinkedList<>();
l.add("asd");
l.add("dsa");
l.add("12233");
l.set(2,"qwe");//改值
System.out.println(l.lastIndexOf("dsa"));//判断最后出现"dsa"的位置
Iterator it2=l.iterator();
while (it2.hasNext())//使用迭代器遍历
System.out.println(it2.next());
for (String ss:l//使用foreach遍历
) {
System.out.println(ss);
}
Vector:
Vector集合底层是一个Object[]数组,初始化容量为10,当数组容量满后,此数组会以2倍的关系扩容。因为Vector底层是由数组实现,因此,查询和修改效率较高。但是Vector是线程安全的。由于Vector线程安全,因此效率没有ArrayList高。
HashSet:
HashSet集合底层是一个HashMap,并且只使用了HashMap的key部分。这个HashMap集合的初始长度是16,当这个集合容量充满0.75时,HashMap会自动扩容会原集合的2倍。并且HashSet是非线程安全的。(HashTable是线程安全的)
TreeSet:
TreeSet(可排序)底层用的是TreeMap,并且只使用了HashMap的key部分。这个HashMap集合的初始长度是11,当这个集合容量充满0.75时,HashMap会自动扩容会原集合的2倍加1。如果使用TreeSet集合,并且存放的数据类型是自定义的话,则必须实现comparable接口的 compareTo方法。(这个方法负责排序)
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add("123");
treeSet.add("456");
treeSet.add("100");
System.out.println(treeSet.size());
treeSet.add("456");
System.out.println(treeSet.size());
treeSet.remove("456");
System.out.println(treeSet.size());
Iterator iterator = treeSet.iterator();
while (iterator.hasNext())
System.out.println(iterator.next());
}
}
Properties:
Properties的Key和value都必须使用字符串类型。
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class PropertiesTest {
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("123","张三");
properties.setProperty("124","李四");
properties.setProperty("93","王五");
properties.setProperty("25","赵六");
System.out.println(properties.getProperty("123"));
Set<Map.Entry<Object, Object>> entries = properties.entrySet();//将propertoes集合转换成set集合方标遍历
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<Object, Object> next = iterator.next();
Object key = next.getKey();
Object value = next.getValue();
System.out.println(key+"--->"+value);
}
}
}
HashMap:
HashMap底层是哈希表。哈希表底层由一个数组和若干个单链表组成。因此HashMap集合了链表和数组的优点和缺点。哈希表的初始长度为16,当这个哈希表容量充满0.75时,HashMap会自动扩容会原表的2倍。如果使用HashMap集合,并且存放的数据类型是自定义的话,必须要重写eauals和hashcode方法。在存数据时哈希表会先使用hashcode方法,将key转换成哈希值,并经过哈希算法转换成数组下标,并和该下标下所有的节点进行比较(equals),如果未重复,则可以插入。非线程安全。
import java.util.*;
public class MapTest {
public static void main(String[] args) {
HashMap<Integer,String> m =new HashMap<>();
m.put(1,"zhangsan");
m.put(2,"liwang");
m.put(3,"wwangwu");
System.out.println(m.size());
System.out.println(m.get(2));
Set<Integer> keys = m.keySet();//先得到key,然后在遍历的过程中用get方法获取value
Iterator<Integer> iterator = keys.iterator();
while(iterator.hasNext()) {
Integer integer = iterator.next();
String s = m.get(integer);
System.out.println(integer+"="+s);
}
for (Integer i:keys
) {
String s = m.get(i);
System.out.println(i+"="+s);
}
System.out.println("-----------------");
Set<Map.Entry<Integer, String>> entries = m.entrySet();
Iterator<Map.Entry<Integer, String>> iterator1 = entries.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
for (Map.Entry<Integer, String> i:entries
) {
System.out.println(i);
}
}
}
使用自定义类型的key,必须重写hashcode和equals和toString方法。key都相同值不同会覆盖。
import java.util.*;
public class HashMapTest {
public static void main(String[] args) {
HashMap<Student, Integer> hashMap = new HashMap<>();
hashMap.put(new Student("zhangsan"),5);
hashMap.put(new Student("lisi"),3);
hashMap.put(new Student("wangwu"),2);
hashMap.put(new Student("zhaoliu"),1);
hashMap.put(new Student("wangwu"),2);
Set<Map.Entry<Student, Integer>> entries = hashMap.entrySet();
Iterator<Map.Entry<Student, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
Hashtable:
Hashtable初始话容量为11,这个集合容量充满0.75时,Hashtable会自动扩容会原集合的2倍+1。线程安全。
TreeMap:
TreeMap底层是一个二叉树,并且这个二叉树是有序的,如果使用TreeMap集合,并且存放的数据类型是自定义的话,则必须实现comparable接口的 compareTo方法。
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapTest {
public static void main(String[] args) {
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1,"zhangsan");
treeMap.put(5,"lisi");
treeMap.put(4,"zhaoliu");
treeMap.put(4,"maqi");
Set<Integer> objects = treeMap.keySet();
Iterator<Integer> iterator = objects.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next+"="+treeMap.get(next));
}
System.out.println("---------------------");
Set<Map.Entry<Integer, String>> entries = treeMap.entrySet();
Iterator<Map.Entry<Integer, String>> entryIterator = entries.iterator();
for (Map.Entry<Integer, String> s:entries
) {
System.out.println(s.getKey()+"="+s.getValue());
}
}
}
HashMap中自定义类型(key)必须实现comparable接口(依次比key,第一个相同值找第二个比)和toString方法。key都相同值不同会覆盖。
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapTest1 {
public static void main(String[] args) {
TreeMap<User, Integer> treeMap = new TreeMap<>();
treeMap.put(new User(5,"zhangsan"),3);
treeMap.put(new User(4,"lisi"),2);
treeMap.put(new User(4,"wangwu"),1);
treeMap.put(new User(3,"zhaoliu"),5);
treeMap.put(new User(3,"zhaoliu"),6);
Set<Map.Entry<User, Integer>> entries = treeMap.entrySet();
Iterator<Map.Entry<User, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
class User implements Comparable<User>{//实现Comparable接口
int id;
String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(User o) {//重写compareTO方法
if(this.id==o.id)
return this.name.compareTo(o.name);
return this.id-o.id;
}
}
IO流
IO流是用来进行硬盘和内存交互的,IO流就像一个管道,将其联通。
IO流有4大类:
InputStream、OutputStream、Reader、Writer。其中前两类是字节流,后两类是字符流。并且1和3是输入流,2和4是输出流。
当我们用完流后,一定要记得关闭流(close()),而输出流在结束时还要使用flash()方法刷新管道,将管道内残留的数据清空,保证我们取得的数据完整。
常用流:
FileInputStream、FileOutputStream ————>文件字节流
FileReader、FileWriter————>文件字符流
BufferedInputStream、BufferedOutputStream————>缓冲字节流
BufferedReader、BufferedWirter————>缓冲字符流
InputStreamReader、OutputStreamWirter————>字节转换流
PrintStream、PrintWirter————>输出流
DataInputStream、DataOutputStream————>数据流
ObjectInputStream、ObjectOutputStream————>对象流
其中常用的有:
FileInputStream、FileOutputStream、ObjectInputStream、ObjectOutputStream、PrintStream
FileInputStream:
由于是字节流,我们使用一个字节数组将读取到的数据进行存放,字节数组长度为4,则表明读一次最多4个字节,在循环外部定义一个标志变量,保存读到数据的个数(也就是长度),使用一个while循环来判断文件中是否还有数据,没有,则循环结束。打印我们每次读到的数据。(如果我们不设置初始的数组,则我们标志变量每次读到的就不是一个长度,而是一个值。)
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest {
public static void main(String[] args) {
FileInputStream fileInputStream=null;
try {
fileInputStream = new FileInputStream("E:\\Java\\JavaProjects\\javase\\IoStream\\src\\test.txt");//绝对路径,相对路径从工程下开始
byte[] bytes = new byte[4];//使用一个byte数组来接收传递的数据
int read = 0;//定义一个标志变量来进行判断,文件中是否还有数据
while ((read=fileInputStream.read(bytes))!=-1)//判断,文件中是否还有数据,fileInputStream.read(bytes))里面存放的是该数组当次读到数据的个数,
System.out.print(new String(bytes,0,read));//输出读到并放在数组中的数据
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream!=null) {
try {
fileInputStream.close();//关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
该txt文件被上面文件输入流读取
admin=1223525235
password=7890
FileOutputStream:
文件输出流相较文件输入流要简单一些。直接调用流的Wirter方法将数据写入,最后刷新流并且关闭流。(ture用来追加内容,不会清空原内容)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutPutStreamTest {
public static void main(String[] args) {
FileOutputStream fileOutputStream=null;
try {
fileOutputStream = new FileOutputStream("test1.txt",true);//输出文件的路径
String s = new String("made in China");
byte[] b1 =s.getBytes();//将字符串转换成byte数组
fileOutputStream.write(b1);//写入文件中
fileOutputStream.flush();//刷新流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream == null) {
try {
fileOutputStream.close();//关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
将一个文件中的数据拷贝另一个文件中(将输入流和输出流结合):
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyTest {
public static void main(String[] args) {
FileInputStream fileInputStream=null;
FileOutputStream fileOutputStream=null;
try {
fileInputStream = new FileInputStream("E:\\a.txt");//输入文件路径
fileOutputStream = new FileOutputStream("E:\\b.txt",true);//输出文件路径,true用来追加,不清空原来存入的内容
byte[] b = new byte[1024*1024];//存放读到的数据
int count=0;//标志变量
while ((count=fileInputStream.read(b))!=-1)//是否能读到数据
fileOutputStream.write(b,0,count);//将存到数组中的数据写入到文件
fileOutputStream.flush();//刷新流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();//关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileReader:
FileReader和FileInputStream类似,FileReader采用字符的方式读,FileInputStream采用字节的方式读。FileReader使用char[]数组存储读到的数据,FileInputStream使用byte[]数组存储读到的数据。
FileWirter:
和FileOutputStream用法完全一样。
ObjectOutputStream:
ObjectOutputStream用来序列化对象。将对象放到集合中,并将该集合使用对象流的writeObject()方法存放。
要想将对象序列化,则必须实现Serializable接口,这个接口只是一个标志,便于JVM识别。
如果我们一段时间后更改了序列化类的内容,则反序列化可能会出现问题,因此在反序列化时我们提前在需要序列化的类中打上序列化Id
,此后,就算我们修改了序列化类的代码内容,反序列化也可成功。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
public class SerializationTest {
public static void main(String[] args) throws IOException {
ArrayList<Object> objects = new ArrayList<>();//创建一个集合,存放我们new出的对象
objects.add(new Serialization(1));
objects.add(new Serialization(1));
objects.add(new Serialization(1));
objects.add(new Serialization(1));
objects.add(new Serialization(1));
objects.add(new Serialization(1));
//指定存放路径
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("log.txt"));
objectOutputStream.writeObject(objects);//将objects集合序列化,序列化后的文件内容是一堆乱码,需要反序列化使用
}
}
class Serialization implements Serializable{
private static final long serialVersionUID = 0001L;//序列化编号,随便起编号,要具有全球唯一性
private int i;
public Serialization(int i) {
this.i = i;
}
@Override
public String toString() {
return "Serialization{" +
"i=" + i +
'}';
}
}
ObjectInputStream:
ObjectInputStream用来反序列化对象。使用流获取到序列化文件,调用流的readObject方法,完成反序列化,并输出。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Iterator;
public class SerializationreadTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {//把异常先抛出去
//序列化文件路径
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("log.txt"));
Object o = objectInputStream.readObject();//读到序列化对象,此处应该是一个集合
if(o instanceof ArrayList){
ArrayList a=(ArrayList)o;//向下转型
for (Object aa:a
) {
System.out.println(aa);//增强for遍历
}
Iterator iterator = a.iterator();//迭代器遍历
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
}
PrintStream:
PrintStream是标准输出流,可以用来记录日志。下面的代码就是这个作用。System.setOut指定输出路径。
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PrintStreamTest {
public static void main(String[] args) {
log("修改文字");
log("修改图片");
}
public static void log(String s){
try {
PrintStream printStream = new PrintStream(new FileOutputStream("log.txt",true));
System.setOut(printStream);//输出到指定文件
Date date = new Date();
//指定格式化后的格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss ssss");
String format = simpleDateFormat.format(date);//格式化Date
System.out.println(format+" "+s);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
IO流配合Properties集合:
两者配合可以拿出文件中“key”对应的“value”。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class IoPropertiesTest throws Exception{
public static void main(String[] args) {
FileInputStream fileInputStream = new FileInputStream("IoStream\\src\\test.txt");//拿到文件
Properties properties = new Properties();//new一个properties集合
properties.load(fileInputStream);//加载文件
System.out.println(properties.getProperty("admin"));//由key获得value值
}
}
admin=1223525235//test.txt内容
password=7890
其他流不常用。不再列举。
线程
进程是资源分配的最小单位,线程是程序执行的最小单位。一个程序就是一个进程,一个进程可以由若干个线程完成。通俗来说,进程指挥线程干活。
线程与线程之间的堆内存和方法区内存资源共享,但是栈区资源不共享。每一个线程都有一个单独的栈。
线程调度模型:
抢占式,按线程的优先级调度
均分式,给各个线程分配相同的时间片
java中线程调度涉及的方法: void setPriority(int newPriority)设置优先级。static void yield()线程让位,该方法会让线程从运行态转为就绪态。void join()将该线程合并到当前正在运行线程,相当于插队,插队的线程运行完再运行当前线程。
实现线程的三种方式:
类直接继承线程,重写线程的run方法。将此类变成线程类。
线程A和线程B同时运行。
public class ThreadExtendsTest {
public static void main(String[] args) {
AThread aThread = new AThread();//new一个线程对象
BThread bThread = new BThread();
aThread.setName("线程A");//线程重命名
bThread.setName("线程B");
aThread.start();//启动线程
bThread.start();
}
}
class AThread extends Thread{//继承线程
@Override
public void run() {//重写线程的run方法
for (int i=0;i<10000;i++)
//Thread.currentThread().getName()该方法为获取当前线程的名字
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
class BThread extends Thread{
@Override
public void run() {
for (int i=0;i<10000;i++)
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
类实现Runnable接口
public class ThreadRunnableTest {
public static void main(String[] args) {
CThread threadC = new CThread();//先new出对应类对象
DThread threadD = new DThread();
Thread cThread = new Thread(threadC);//将该对象变成线程对象
Thread dThread = new Thread(threadD);
cThread.setName("线程C");
dThread.setName("线程D");
cThread.start();
dThread.start();
}
}
class CThread implements Runnable{//实现Runnable接口
@Override
public void run() {
for(int i=0;i<1000;i++)
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
class DThread implements Runnable{
@Override
public void run() {
for(int i=0;i<1000;i++)
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
类实现Callable接口
实现Callable接口,我们会得到一个返回值,该返回值使用FutureTask.get()方法获取
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadCallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
EThread threadE = new EThread();//new一个对应类对象
FThread threadF = new FThread();
FutureTask futureTask1 = new FutureTask<>(threadE);//new FutureTask对象是为了获取线程返回值
FutureTask futureTask2 = new FutureTask<>(threadF);
Thread eThread = new Thread(futureTask1);//加入线程
Thread fThread = new Thread(futureTask2);
eThread.setName("E进程");
fThread.setName("F进程");
eThread.start();
fThread.start();
System.out.println(futureTask1.get());//获得返回值
System.out.println(futureTask2.get());
}
}
class EThread implements Callable {
int i=100;
@Override
public Object call() throws Exception {
return i;
}
}
class FThread implements Callable {
int i=200;
@Override
public Object call() throws Exception {
return i;
}
}
在以后的开发环境中我们面对的都是多线程的运行环境,在这种情况下,如果多个线程对象同时修改一个共享数据,可能会造成数据安全问题。
由此引出线程同步机制:
多个线程若想同时操作一个共享数据需要排队。牺牲一部分效率,但保证了数据安全。
线程同步机制会让用户的体验降低。
解决线程同步问题:
使用局部变量代替实例变量和静态变量,局部变量在栈区,没有安全问题。
如果是实例变量的话,可以创建多个对象,让每个线程都有单独的对象可调。
如果前两种都无法解决,则只能使用synchronized。
synchronized为共享对象”加锁“
例如:
生产者和消费者共享一个仓库,只有仓库中有库存,消费者才可以消费,同样的,当仓库满后,生产者不能再生产。(使用线程同步机制后,在一个时间点上要么生产,要么消费)
wait()和notify()都是对象级的方法,sleep睡眠的是当前线程,使用引用.interrupt()打断睡眠。
import java.util.ArrayList;
public class HomeWorkTest {
public static void main(String[] args) {
ArrayList<Object> objects = new ArrayList<>();//new一个仓库对象
Producter producter = new Producter(objects);//将仓库共享给生产者
Customer customer = new Customer(objects);//将仓库共享给消费者
Thread threadA = new Thread(producter);//生产线程
Thread threadB = new Thread(customer);//消费线程
threadA.setName("生产者");
threadB.setName("消费者");
threadA.start();//启动
threadB.start();
}
}
class Producter implements Runnable{
private ArrayList objects;
public Producter(ArrayList objects) {
this.objects = objects;
}
@Override
public void run() {
while (true) {//一直生产
synchronized (objects) {//为仓库加锁,锁的对象是仓库
if (this.objects.size() > 0) {//当仓库中有库存时,停止生产
try {
this.objects.wait();//交出自己的锁,下面代码不会再执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object o = new Object();
objects.add(o);//添加库存
System.out.println(Thread.currentThread().getName()+o);//输出
this.objects.notify();//唤醒其他线程
}
}
}
}
class Customer implements Runnable{
private ArrayList objects;
public Customer(ArrayList objects) {
this.objects = objects;
}
@Override
public void run() {
while (true) {
synchronized (objects) {
if (objects.size() == 0) {//没有库存,停止消费
try {
this.objects.wait();//交出自己的锁,下面代码不会再执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + objects.get(0));//输出
objects.remove(0);//移出库存
objects.notify();//唤醒其他线程
}
}
}
}
如果同步机制使用不当会造成死锁,死锁不报错误,程序也不终止。
线程A先调用o1对象,线程B先调用o2对象,当线程A去调用o2时发现o2已经被锁,当线程B去调o1时发现o1被锁。
public class DeadLockTest {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread threadA = new ThreadA(o1, o2);
Thread threadB = new ThreadB(o1, o2);
threadA.setName("线程A");
threadB.setName("线程B");
threadA.start();
threadB.start();
}
}
class ThreadA extends Thread{
private Object o1;
private Object o2;
public ThreadA(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){//锁住o1
try {
Thread.sleep(5000);//设置休眠时间,使o2有大几率被B进程锁住
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){//锁住o2
System.out.println(Thread.currentThread().getName()+"------正在运行");
}
}
}
}
class ThreadB extends Thread{
private Object o1;
private Object o2;
public ThreadB(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){//锁住o2
synchronized (o1){//锁住o1
System.out.println(Thread.currentThread().getName()+"------正在运行");
}
}
}
}
定时器(timer)
定时器是在特定的时间执行特定的动作,并且可以设置动作重复执行的时间。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();//new一个定时器对象
TimerTask01 timerTask = new TimerTask01();//new一个定时器任务
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstDate = simpleDateFormat.parse("2021-11-16 11:15:00");//格式化时间
timer.schedule(timerTask,firstDate,1000);//调用定时器方法(任务,执行时间,重复执行间隔)
}
}
class TimerTask01 extends TimerTask {//定时器任务继承TimerTask类
@Override
public void run() {
Date time = new Date();//获取当前时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(time);//格式化当前时间
System.out.println(format+"完成备份");
}
}
守护进程
只要我们的用户线程存在则守护线程会一直执行,一般守护线程都是一个死循环,当所有用户线程结束后,守护线程也会结束。
public class DeamonTest {
public static void main(String[] args) {
MyDeamon myDeamon = new MyDeamon();
Thread thread = new Thread(myDeamon);
thread.setDaemon(true);//设置该线程为守护线程
thread.setName("守护线程");
thread.start();
for (int i=0;i<1000;i++)
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
class MyDeamon implements Runnable{
@Override
public void run() {
while(true)
System.out.println(Thread.currentThread().getName()+"正在运行!");
}
}
反射机制
反射机制可以让我们直接操纵字节码文件,可以增加我们程序的扩展性。
在java中获取class的三种方式:
Class c = Class.forname("类名");//第一种
Class c = 对象.getClass();//第二种
Class C = 数据类型.class;//第三种
JDK中默认有三个类加载器:
启动类加载器(rt.jar)、扩展类加载器(ext/*.jar)、应用类加载器(classpath)。从前往后的顺序调用,其中启动和扩展被称为双亲委派机制。
反射机制和流加载路径相关问题
多种文件加载方法,起始加载路径可能不同
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.ResourceBundle;
public class ReflectionTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println("-----------第一种------------------");
InputStream fileInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("Test.properties");//流加载法,不分操作系统,从src下开始写
/*System.out.println("------------第二种-----------------");
String path = Thread.currentThread().getContextClassLoader().getResource("Test.properties").getPath();//从src下开始写
FileInputStream fileInputStream = new FileInputStream(path);
System.out.println("----------第三种---------------");
FileInputStream fileInputStream = new FileInputStream("reflection\\src\\Test.properties");//从工程下开始写
*/
Properties properties = new Properties();
properties.load(fileInputStream);
fileInputStream.close();
String file = properties.getProperty("file");
Class name = Class.forName(file);
Object o = name.newInstance();//实例化的一个对象
if (o instanceof User){
User u =(User)o;
u.setId(100);
System.out.println(u);
}
}
}
流加载的文件
file=User
User类
public class User {
int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
'}';
}
}
使用反射机制new对象并设置属性,获取属性。(资源绑定器是最方便的获取文件方法,但是获取的文件后缀要是properties)
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.ResourceBundle;
public class ReflectionTest4 {
public static void main(String[] args) throws Exception {
ResourceBundle bundle = ResourceBundle.getBundle("Test");//资源绑定器绑定properties文件,路径从src下开始。不需要带properties后缀
String file = bundle.getString("file");//获得文件“=”号右侧与之对应的内容
Class forName = Class.forName(file);//获取类,如果直接写相对路径的话也是从src下开始,一直写到类,中间用“.”连接
Object obj = forName.newInstance();//new实例
Field id = forName.getDeclaredField("id");//得到id属性
id.set(obj,1001);//赋值
Field name = forName.getDeclaredField("name");
name.setAccessible(true);//破除封装
name.set(obj,"张三");
System.out.println(id.get(obj)+" "+name.get(obj));
}
}
使用反射机制new对象并调用实例方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest5 {
public static void main(String[] args) throws Exception {
Class methodloginTest = Class.forName("MethodloginTest");
Object obj = methodloginTest.newInstance();
Method method = methodloginTest.getDeclaredMethod("login", String.class, String.class);//得到方法
Object o = method.invoke(obj, "1001", "password");//调用方法,传初值(invoke是调用)
if (o instanceof Boolean){
boolean oo = (boolean)o;
System.out.println((oo?"登录成功":"登陆失败"));}
Method loginout = methodloginTest.getDeclaredMethod("loginout");
loginout.invoke(obj);//调用方法
}
}
class MethodloginTest {
public boolean login(String admin,String password){
if (admin.equals("1001")&&password.equals("password"))
return true;
else
return false;
}
public void loginout(){
System.out.println("退出成功!");
}
}
调用newInstance方法创建对象时自动调用该类的无参构造方法。
import java.lang.reflect.Constructor;
public class ReflectionTest7 {
public static void main(String[] args) throws Exception{
Class user = Class.forName("User");
Object obj = user.newInstance();//自动调用无参构造
Constructor constructor = user.getDeclaredConstructor(int.class, String.class);
Object newInstance = constructor.newInstance(123, "abc");//使用有参构造创建对象
System.out.println(newInstance);
}
}
注解
注解(Annotation)是一种引用数据类型,编译之后生成class文件。
注解可以出现在,类、方法、变量、属性上。
定义注解:
[修饰符列表] @interface 注解名{
数据类型 变量名1(); 数据类型可以是基本类型,也可以基本数据类型的数组,还可以是类和String和String数组
数据类型 变量名2();
}
如果注解的变量名是“value”并且该注解只有一个属性则使用注解时不需要”value=值“,只需要“值”即可。数组中如果只有一个值的话”{}“可以省略。
元注解:
常用的有:Target、Retention。其中Target用来设置该注解可以注解的目标。Retention用来保存注解最后保存的位置。(保存位置一般为source或class或runtime(runtime属性可以让我们使用反射机制))
常用注解:Override、deprecated。
被Override注解修饰的方法需要重写父类的方法。
被deprecated注解修饰的类或方法或其他。。。会显示已过时。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)//该注解只能放到类上
public @interface MyAnnotationClass {
int name1();
String name2();//注解中可以有属性
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//该注解只能放到属性上
@interface MyAnnotationField{
int value() default 123;//value属性赋值时只需要在注解中写值就可以了
//String[] s();数组中只有一个值的话不用加大括号
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)//该注解只能放到构造方法上
@interface MyAnnotationConstructor{}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//该注解只能放到方法上
@interface MyAnnotationMethod{}
使用注解、获取自定义注解值
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@MyAnnotationClass(name1 = 123,name2="admin")//使用注解,要给注解中的属性赋值
public class AnnotationTest1 {
//测试
public static void main(String[] args) throws Exception{
Class forName = Class.forName("AnnotationTest1");
boolean ClassAnnotationPresent = forName.isAnnotationPresent(MyAnnotationClass.class);//判断该类是否有对应注解
if(ClassAnnotationPresent){
Annotation classAnnotation = forName.getAnnotation(MyAnnotationClass.class);//获取类注解值
System.out.println(classAnnotation);
}
Field id = forName.getDeclaredField("id");
boolean idAnnotationPresent = id.isAnnotationPresent(MyAnnotationField.class);//判断该属性是否有对应注解
if(idAnnotationPresent){
MyAnnotationField idAnnotation = id.getAnnotation(MyAnnotationField.class);//获取属性注解值
System.out.println(idAnnotation);
}
Method dosome = forName.getDeclaredMethod("dosome");
boolean dosomeAnnotationPresent = dosome.isAnnotationPresent(MyAnnotationMethod.class);//判断该方法是否有对应注解
if (dosomeAnnotationPresent){
MyAnnotationMethod dosomeAnnotation = dosome.getAnnotation(MyAnnotationMethod.class);//获取方法注解值
System.out.println(dosome);
}
}
@MyAnnotationField(10001)
private int id;
private String name;
@MyAnnotationConstructor
public AnnotationTest1(int id, String name) {
this.id = id;
this.name = name;
}
@MyAnnotationMethod
public void dosome(){
System.out.println("方法被调用!");
}
}