javase 学习笔记

老杜,永远的神!!!

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类型都可以
	case1:
		java语句;
		break//如果不写break会出现case击穿,一直击穿到找到break或者找到分支最后才能结束
	case2:
		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("方法被调用!");
    }
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值