javaSE进阶

IDEA相关快捷键

快速生成main方法:psvm

快速生成System.out.println(): sout

删除一行:ctrl+y

运行:代码上右键run或者点右上绿色箭头 或ctrl+shift+F10

左侧窗口,右键展开,左键收敛

任何新增/新建/添加的快捷键是:alt+insert

窗口变大,变小:ctrl+shift+F12

切换java程序:alt+左右箭头

查看函数所需参数光标括号内然后ctrl+p

单行注释:ctrl+/ 多行注释:ctrl+shift+/

ctrl+鼠标点击方法名、变量名定位方法、属性、变量

复制一行:ctrl+d

alt+回车 纠正错误

一、面向对象

final修饰的类无法被继承

final修饰的局部变量不能重新赋值

final修饰的方法无法被覆盖,被重写

final修饰的变量只能赋一次值

final Person p= new Person();

以上代码内存地址不能再改,但是内存地址里的数据可以被修改

final修饰实例变量,系统将不在自动赋值默认值,要求必须程序员手动赋值。这个手动赋值,在变量后面赋值可以,在构造方法中赋值也可以

final修饰的实例变量一般与static联合使用,static final 联合修饰的变量称为“常量”。常量建议全部大写,每个单词直接采用下划线衔接。

常量和静态变量一样,区别是常量的值不能变。

常量一般都是公开的:public的。

public static final double PI=3.1415926

抽象类

  1. 什么是抽象类?(类和类直接具有共同特征,将这些共同特征提取出来,形成的就是抽象类,类本身是不存在的,所以抽象类无法创建对象)
  2. 抽象类属于什么类型?(引用数据类型)
  3. 抽象类怎么定义?[修饰符列表] abstract class 类名{ 类体;}

抽象类是无法实例化的,所以抽象类是用来被子类继承的。

抽象类的子类可以是抽象类

抽象类有构造方法,这个构造方法是供子类使用的

抽象方法

定义:抽象方法表示没有实现的方法,没有方法体的方法。例如public abstract void doSome();

特点1:没有方法体,以分号结尾

特点2:前面的修饰符列表中有abstract关键字

抽象类中不一定要有抽象方法,抽象方法必须在抽象类中

非抽象类继承抽象类必须将抽象方法实现(实现的意思是方法重写)

这里出现一个问题,为什么要有抽象类,我直接继承非抽象父类不是一个作用?

面向抽象编程,不要面向具体编程,降低程序耦合度,提高程序扩展力。这种编程思想符合OCP原理。

接口的基础语法

接口是一种引用数据类型,编译后是一个class字节码文件

接口是完全抽象的,抽象类是半抽象。

[修饰符列表] interface 接口名{}

接口之间支持多继承

接口中包含两部分内容,一部分为常量,另一部分是抽象方法

接口中的所有元素都是public修饰的

接口中的抽象方法定义时,public abstract可以省略

接口中的方法不能有方法体

接口中的常量,public static final可以省略

类和类之间叫做继承,类与接口之间叫做实现

继承用extends,实现用implements

当一个非抽象类实现接口的话,必须将接口中所有的抽象方法全部实现。

一个类可以实现多个接口

向下转型养成习惯加上instanceof

接口在开发中的作用类似于多态在开发中的作用

类型与类型之间的关系

is a “继承关系”

has a 表示“关联关系”,以“属性”的形式存在

like a cooker like a FoodMenu 厨师像一个菜单一样 ,“实现关系” 实现关系通常是:类实现接口。

抽象类与接口有什么区别?

  1. 抽象类是半抽象的,接口是完全抽象的
  2. 抽象类有构造方法,接口没有
  3. 接口和接口之间支持多继承,而类与类之间只能单继承
  4. 一个类可以同时实现多个接口,一个抽象类只能继承一个类
  5. 接口中只允许出现常量和抽象方法

(接口的使用比抽象类多,接口一般都是对“行为”的抽象)

包和import

包命名规范:公司域名倒序+项目名+模块名+功能名

对于带有package的java程序怎么编译,不能采用之前的方式,类名改为com.bjpowernode.javase.chapter17.Helloworld

package com.bjpowernode.javase.chapter17;

public class HelloWord{public static void main(Strings[] args){

​		S.p("sfasfds");

}

}

import什么时候用?a和b类不在同一个包下

package 后跟位置如何class生成在这个位置上

lang包下自动导入,所以string,int等类不需要import

import的语句只能出现在package语句之下,class声明语句之上,还可以采用星号的方式,导入相关位置下的所有类。

带包名怎么运行 javac -d . xxx.java

访问控制权限

  1. private 私有 只能在本类中访问
  2. public 公开 在任何位置都可以访问
  3. protected 受保护
  4. 默认

受保护和默认的区别是?

在同个包下 除私有外都可以,不在同一个包下,protected修饰的变量只能在子类、本类和同包中访问,而默认表示只能在本类,以及同包下访问
在这里插入图片描述
修饰类只能是public和默认

重写object中的方法

String类已经重写了toString equals等方法

java中基本数据类型可以使用“==”判断

java中所有引用数据类型统一使用equals方法判断

字符串String不属于基本数据类型

finalize方法

这个方法是protected修饰的,该方法只有一个方法体,且里面没有代码,这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用finalize()方法。

如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中。

static{}静态代码块是在类加载时刻执行,并且只执行一次

System.gc():建议启动垃圾回收器

hashCode方法返回内存地址哈希值

内部类

重点匿名类

public class test{
    public static void main(String[] args){
        MyMath mm = new MyMath();
        Compute c = new ComputeImp();
       
        mm.mySum(c,100,200);
    }
}
interface Compute{
    int sum(int a,int b);
}
class ComputeImp implements Compute{
    public int sum(int a,int b){
        return a+b;
    }
}
class MyMath{
    public void mySum(Compute c,int x,int y){
        int result = c.sum(x,y);
        S.p("结果是"+result);
    }
}
//匿名类写法
public class test{
    public static void main(String[] args){
        MyMath mm = new MyMath();
        mm.mySum(new Compute(){
            public int sum(int a,int b){
                return a+b
            }
        },100,200)
    }
}
interface Compute{
    int sum(int a,int b);
}

class MyMath{
    public void mySum(Compute c,int x,int y){
        int result = c.sum(x,y);
        S.p("结果是"+result);
    }
}

不建议使用匿名内部类,可读性差,且无法重复使用

二、数组

数组是引用数据类型,不属于基本数据类型,数组的父类是object。存储在堆内存中

数组中的元素内存地址是连续的

所有的数组都是拿第一个小方框的内存地址作为整个数组对象的内存地址

数组中的每个元素都是有下标的,下标从0开始,以1递增

数组的优缺点

优点:查询、查找、检索某个下标上的元素时效率极高。可以说是查询效率最高 的一个数据结构

  1. 每一个元素的内存地址在空间存储上是连续的
  2. 每个元素的类型相同,所以占用空间大小一样
  3. 知道第一个元素内存地址,知道每个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出 某个下标元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的

缺点:一、为了保证数组每个元素的内存地址连续,在插入或删除元素时,效率较低,因为随机增删涉及到后面元素统一向前或向后位移的操作。二、数组不能存储大数据量,因为很难在内存空间上找到一块特别大的连续的内存空间
在这里插入图片描述
语法格式:

int[] array1;
double[] array2;
boolean[] array3;...
// 静态初始化语法格式
int[] array = {100,200,300,55};
//动态初始化语法格式
int[] array = new int[5];//长度为5,每个元素默认为0
String[] names = new String[];

当你确定需要存储什么数据时用静态,不确定时用动态

数组扩容

先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组。效率较低,故尽量减少数组扩容操作。

System.arraycopy使用方法

//拷贝源
int[] src ={1,2,3,4,5}
//拷贝目标
int[] dest = new int[20];
System.arraycopy(src,1,dest,3,2)//第二个参数是拷贝源的下标,第四个参数是拷贝到目标的位置下标,第五个参数拷贝长度
二维数组
int[][] a = {{},{},{},{}};

一般写一个方法都要重写toStrinng和equals

println(引用) 会自动调用引用的toString()方法

存储在数组中的程序还有个问题,断电数据就没了,因为是在内存中,通过学习数据库可以将数据存储在硬盘中,这样能更加有效的保存数据。

Arrays工具类

Arrays.sort(arr);默认从小到大排序

常见的算法

冒泡排序算法,选择排序算法,二分法查找

冒泡排序

依次比较,先找出最大的,然后找出第二大,依次循环。

比如数组[3,6,9,4,2]->[3,6,9,4,2]->[3,6,9,4,2]->[3,6,4,9,2]->[3,6,4,2,9]接下来对[3,6,4,2]进行冒泡依次循环

选择排序

选择排序比冒泡排序的效率高,高在交换位置的次数上,选择排序的交换位置是有意义的。循环一次,然后找出参加比较的这堆数据中最小的,拿着这个最小的值和最前面的数据交换位置

与冒泡排序相比,比较次数不变,交换次数减少。

二分法查找

该查找方法是建立在排序数据之上。

String字符串储存原理

只要用“”括起来的字符串都会出现在方法区的字符串常量池中,new String 的字符串不仅在字符串常量中有,堆内存也会出现一个对象指向方法区的字符串常量池

String s1=“abc”;

s1里面保存的不是“abc”字符串,s1里面保存的是“abc”字符串对象的内存地址, 该地址指向方法区中的字符串常量池,字符串常量池也有一个对象

String s2 = “abc”; s1==s2返回结果是True,因为这两个指向同个内存地址,s1和s2在栈中没有创建对象所以不在堆内存。

valuOf将非字符串转化为字符串,若是对象则调用该对象的toString方法。

用+号进行拼接存在的问题。因为java中的字符串是不可变的,每一次拼接都会尝试新字符串,这样会造成大量的方法区内存,造成内存空间的浪费。

StringBuffer 底层实际上是一个byte[]数组,初始化容量是16。拼接字符串可以采用创建StringBuffer对象,使用该对象的方法append,打印输出。

StringBuffer是线程安全的,StringBuilder是非线程安全的

八种包装类型
在这里插入图片描述

基本数据类型转换为引用数据类型(装箱)

引用数据类型转换为基本数据类型(拆箱)

integer x=100;自动装箱

int y =x; 自动拆箱

System.out.println(x+1);自动拆箱

重要的面试题关于integer的。

java中为了提高程序的执行效率,将[-128,127]之间所有 的包装对象提前创建好,放到一个方法区的“整数型常量池”中,目的是只要用这个区间的数据不需要new,直接从整数型常量池中取出来。

Integer x=127;

Integer y=127;

x==y返回的true

Integer x=128;

Integer y=128;
x==y返回的是false,此处因为x和y保存到堆内存的地址不同,故返回的是false

Integer.parseInt(String s);

将字符串转化为int
在这里插入图片描述
时间对象

Date nowTime = new Date(); //调取当前时间
System.out.println(nowTime);

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //修改时间格式
       System.out.println(sdf.format(nowTime));

//获取昨天的此时的时间
Date time2 = new Date(System.currentTimeMillis()-1000*60*60*24);
String str =sdf.format(time2);
System.out.println(str);

Date time = new Date(1);

Date对象加一个参数代表从1970.1.1 起多这个参数的毫秒数

字符串转化为Date类型

String time ="2008-08-08 08:08:08 888";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); //修改时间格式
Date dateTime =sdf.parse(time);
System.out.println(dataTime);

获取1970.1.1 到当前时间的毫秒数

long nowTimeMillis = System.currentTimeMillis();
System.out.println(nowTimeMillis)

该方法统计一个方法执行所耗费的时长

public class dateTest01 {
    public static void main(String[] args) {
        long begin = System.currentTimeMillis();
        print();
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end-begin)+"毫秒");
    }
    public static void print(){
        for(int i = 0;i<1000;i++){
            System.out.println(i);
        }
    }
}

System类的相关属性和方法
在这里插入图片描述

枚举类型

枚举是一种引用数据类型,语法 enum 枚举类型名{枚举值1,枚举值2…}

结果只有两种建议使用布尔值,超过两种使用枚举类型。例如颜色,星期…

UML是一种统一建模语言,一种图标式语言,不只是java中使用,只要是面向对象的编程语言,都有UML。一般是软件架构师或者系统分析师使用。

UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等。

异常在java中以类和对象的形式存在,那么异常的继承结构是怎么样的,可以用UML图来描述一下继承结构

object下有Throwable,该类又有两个分支:error(不可处理,直接退出JVM)和Exception(可处理的)Exception下有两个分支:exception的直接子类(编译时异常),RuntimeEception(运行时异常)

  • 运行时异常,发生概率较低 未受检异常UnCheckedException

  • 编译时异常,发生概率较高,受检异常CheckedException

    java有两种方式处理异常,第一种,在方法声明的位置上,使用throws关键字。第二种,使用try…catch语句进行异常的捕捉

public static void doSome() throws ClassNotFoundException{

}

一般main方法的异常建议采用try…catch… 进行捕捉,不再继续上抛了。 如果希望调用者来处理,选择throws上报,其他情况使用捕捉的方式

异常对象的两个方法

String msg =e.getMessage();

e.printStackTrace

finally 关键字与try catch连用,有时候有些代码出现异常时下面的代码运行不了,而某些代码必须运行,例如创建输入流对象之后需要关闭流对象,此时就要用到finally

通常用在资源的关闭与开启

finally里的代码几乎都会执行,除了try中出现退出JVM操作。
在这里插入图片描述
179 异常在实际开发中的作用

重写之后的方法不能比重写之前的方法抛出更多的异常,可以更少

集合

集合中存储的是引用,java对象的内存地址,所以不能直接存储基本数据类型和java对象。

java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到不同的数据结构当中,数据结构包括数组、二叉树、链表、哈希表…

需背会集合的继承结构
在这里插入图片描述在这里插入图片描述

java中集合分为两大类

  • 一类是单个方式存储元素,这一类集合中超级父接口:java.util.Collection
  • 一类是以键值对的方式存储元素,这一类的超级父接口:java.util.Map;

Iterator it = “Collection对象”.iterator();

it是迭代器对象
在这里插入图片描述

 Collection c2 = new HashSet(); //无序不可重复 ,无序:存进去和取出来的不一定相同
 Collection c = new ArrayList();//ArrayList集合,有序可重复

contains方法和remove方法底层调用了equals方法。

放在集合中的类型,一定要重写equals方法,去比较内容而不是内存地址。

自己创建的对象需重写equals方法,基本数据类型无需,因为已经自动重写

在这里插入图片描述

集合结构只要发生改变,迭代器必须重新获取。

迭代器类型使用remove则不会报错

对集合进行remove操作,iterator相当于对集合拍一个快照,那么集合少了一个元素,快照就与集合元素对应不上了,程序报错。迭代器类型使用remove,会先删除快照元素,紧跟着删除集合元素,那么此时快照与集合相互对应,故不会报错。

或者说迭代器去删除时,会自动更新迭代器,并且更新集合。

ArrayList集合的扩容是原容量的1.5倍。尽量减少对数组的扩容,给定一个预估计的初始化容量。默认原容量为10.

单向链表
在这里插入图片描述

优点:随机增删元素效率较高,因为不涉及到大量元素位移

缺点:查询效率较低,每一次查询某个元素的时候都需要从头节点开始往下遍历,相比于数组不能通过数学表达式查询元素的内存地址

链表也有下标和数组一样。

LinkedList源码分析

双向链表

在这里插入图片描述

vector
  1. 底层是一个数组
  2. 初始化容量:10
  3. 扩容之后是原容量的2倍
  4. ArrayList集合扩容特点:扩容后是原容量的1.5倍
泛型机制

泛型是属于编译阶段新特性,在运行阶段没作用。

泛型的优势

  1. 集合中存储的元素类型统一了
  2. 从集合中取出的元素类型是泛型指定的类型(默认取出类型是Object),不需要进行大量的“向下转型”!
List<Animal> myList = new ArrayList<Animal>();

集合中只存储一种类型时运用泛型!

public class GenericTest02 {
    public static void main(String[] args) {
        MyIterator<String> mi = new MyIterator<>();
        String s1 = mi.get();
    }
}

class MyIterator<T> {
    public T get(){
        return null;
    }
}

这里的T是标识符随便写,之后实际运用时写入具体类型,比如例子中写入String,那么T便表示为String,该公共类中的方法get的返回类型为string。

java源代码中能中经常出现和 E为element。T为type

foreach是for循环加强版

int[] arr = [33,565,6,77,85,33]
for(int data:arr){
    System.out.println(data);
}
//foreach有一个缺点:没有下标

Hashset

无序不可重复,但是存储的远古三可以自动按照大小顺序排序,可排序集合

这里的无序指的是没有下标,存入时没有顺序,取出时有顺序

HashMap集合

  1. HashMap集合底层是哈希表/散列表的数据结构
  2. 哈希表是一个数组和单向链表的结合体,数组在查询方面效率很高,随机增删方面效率很低,单项链表在随机增删方面效率高,在查询方面效率很低,哈希表结合上面两种数据结构的优点
  3. HashMap集合底层的源代码:

public class HashMap{

Node<K,V> table;

static class Node<K,V>{

final int hash; //哈希值

final k key;

V value;

Node<K,V> next; //下一个节点的内存地址

}

}

HashMap集合的key,会先后调用两个方法,一个方法是hashCode,另一个方法是equals,那么这两个方法都需要重写

无序的原因是不一定挂到哪个单项链表上,像窗帘,列表下挂着单向链表。

同一个单向链表上的哈希值是一样的,key是不同的

假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的, 是散列分布均匀的

如果hashCode返回值为固定值,则hashmap变为单向链表,若返回 值都不一样,hashmap变为列表。

如果一个方法equal方法重写了,那么hashCode方法也必须重写,并且equals方法返回如果是true,hashCode方法返回的值必须一样,那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的

放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法

HashMap和Hashtable的区别

HashMap可以放入null,Hashtable不行
在这里插入图片描述
在这里插入图片描述

TreeSet可以对字符串进行排序,对于自定义的对象,若没有定义排序规则,则不能进行排序。

定义排序规则方法如下(Comparable是核心):

class Vip implements Comparable<Vip>{
    String name;
    int age;

    public Vip(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Vip{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Vip v) {
        if(this.age == v.age){
            return this.name.compareTo(v.name);
        }else{
            return this.age -v.age;
        }

    }
}

自平衡二叉树数据结构

在这里插入图片描述
TreeSet集合中可排序的两种方式
在这里插入图片描述

IO流概述

Input,Output,通过IO可以完成硬盘文件的读和写

在这里插入图片描述
在这里插入图片描述

FileInputStream 和FileOutputStream是读写字节流,什么文件都可以读取,必须往byte字节中读

FileReader和FileWriter是读写字符流,只能读取普通文本,往字符char中读写

BufferedReader自带缓冲区,不用设置

在这里插入图片描述

序列化是对对象的操作,核心作用是对象的保存和创建

ObjectInputStream

ObjectOutputStream

参与序列化和反序列化的对象,必须实现serializable接口

先用ObjectOutputStream序列化生成一个字节码文件,再用ObjectInputStream反序列化。

一次可以序列化多个对象,将对象放到集合中,序列化集合便可

如果不想要某个变量序列化,可以使用关键字transient(游离)

implements Serializable 实现该接口java虚拟机会自动生成序列化,该方法有一缺陷:每次更改后就会更新序列号,后续不能修改代码,java虚拟机会判定该对象为一个全新的类。

解决方案:凡是一个类实现了Serializable,建议给该类提供一个固定不变的序列版本号。手动将序列化版本号手动写出来。

IDEA工具自动生成版本序列号,setting->inspection->Serializable class without"serialVersionUID"
在这里插入图片描述

多线程

进程是一个应用程序

线程是一个进程中的执行场景/执行单元

一个进程可以启动多个线程。

例子:对于java程序来说,在DOS命令窗口输入java HelloWorld 回车之后,会先启动JVM,JVM就是一个进程,JVM再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

进程a和进程b的内存独立不共享(阿里和字节的资源不会共享)

在java语言中,线程a和线程b,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈

在这里插入图片描述

具体例子

定义一个MyThread继承Thread,myThread.start();方法作用为启动一个分支线程,在JVM开辟一个新的栈空间,这段代码瞬间就结束了。此时,线程就启动成功了,启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部,run和main是平级的。

实现进程有两种方式

第一种方式:编写一个类,直接继承java.lang.Thread,并重写run方法。

Public class MyThread extends Thread{

​ public void run(){

}}

MyThread t= new MyThread();

t.start;

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法

Public class MyThread implements Runnable{

	public void run(){

}}
MyRunnable r = new MyRunnable();
MyThread t= new MyThread(r);

t.start;

第二种方式更常用,因为java只支持单继承,但接口可以实现多个,采取第二种方式不仅实现该功能,还能留有余地去继承。

线程的生命周期

在这里插入图片描述

怎么获取当前线程对象?Thread t = Thread.currentThread();

获取线程对象的名字 线程对象.getName()

修改进程对象的名字 线程对象.setName()

static void sleep

在这里插入图片描述

Thread.sleep(毫秒) 1秒=1000毫秒

怎么叫醒一个正在睡眠的线程

TreadTest08

合理终止线程 TreadTest09

线程不安全的例子
在这里插入图片描述

满足以下三个条件,可能会出现线程安全问题

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

解决方案:线程排队,用排队执行线程安全问题,这种机制被称为:线程同步机制

异步编程模型:两个线程各自执行各自的,其实就是多线程并发

同步编程模型:在线程t1执行的时候,必须等待t2线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型

线程同步机制的语法synchronized(){},注意小括号里面的参数必须是多线程共享的数据,才能达到多线程排队

在这里插入图片描述

java三大变量

  1. 实例变量,在堆中
  2. 静态变量,方法区
  3. 局部变量,在栈中

其中局部变量永远不会存在线程安全问题,因为局部变量在栈中,所以局部变量永远不会共享。

还有常量,是固定不变的,所以也没有线程安全问题

public synchronized void withdraw(double money){

synchronized出现在实例方法上,一定锁的是this。故该方式不灵活。缺陷2,synchronized出现在实例方法上,表示整个方法体都需要同步,可能无故扩大同步范围,降低程序执行效率。

如果使用局部变量,建议使用StringBuilder(非线程安全),因为局部变量不存在线程安全问题

ArrayList是非线程安全的

Vector是线程安全的

HashMap HashSet 是非线程安全

Hashtable是线程安全的

总结:

synchronized三种写法

  1. synchronized(线程共享对象){}
  2. 在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
  3. 在静态方法上使用synchronized表示找类锁。类锁永远只有一把。对象锁:一个对象一把锁,100个对象100吧锁。类锁:100个对象,也可能只有一把类锁。

开发中尽量不要嵌套使用synchronized

要会写死锁deadlock

在开发中如何解决线程安全问题?

  1. 尽量使用局部变量代替“‘实例变量和静态变量”
  2. 如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,不存在安全问题)
  3. 如果不能使用局部变量,对象也不能创建多个,这个时候只能使用synchronized,线程同步机制
守护线程

java语言中线程分为两大类

  1. 用户线程
  2. 守护线程(后台线程)

其中具有代表性的是:垃圾回收线程(守护线程)

守护线程的特点:一般守护线程是一个死循环,所有用户线程只要结束,守护线程自动结束。

主线程main方法是一个用户线程

一般用在什么地方呢?

每天00.00的时候系统数据自动备份,这个需要使用到定时器,我们可以将定时器设置为守护线程

守护线程

使用t.setDaemon(true);

线程设置为死循环便可

定时器

间隔特定的时间,执行特定的程序。

  • 每周进行银行账户总账操作
  • 每天进行数据备份操作

在实际开发中用的较多的是spring框架中提供的springTask框架。

系统委派一个线程去执行一个任务,该线程执行完成后,可能会有一个执行结果,怎么拿到这个结果?

实现Callable接口方法,缺点是效率较低,在获取t线程执行结果时,当前线程受阻塞。

wait和notify

Object o =new Object;

o.wait();

让正在对象o上运转的线程进入等待状态,该状态无期限,只能等待唤醒。

o.notify();可以让o对象上等待的线程唤醒。

还有个notifyAll()唤醒所有处在等待的线程。

具体代码见ThreadTest16

在这里插入图片描述

注意锁定对象后,对象不能改动!可看下面的线程例子

https://www.jianshu.com/p/41ab99e6ac2c

反射机制

通过java语言中的反射机制可以操作字节码文件,可以读和修改字节码文件,通过反射机制可以操作代码片段

在这里插入图片描述

获取Class 的三种方式

在这里插入图片描述

反射机制更加灵活,比如改变配置文件,读取后可以不改变代码。

类路径下获取某个文件的绝对路径,如下
在这里插入图片描述

java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容,使用以下这种方式的时候,属性配置文件xxx.properties必须放置类路径下。

public class ResourceBundleTest {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo1");
        System.out.println(bundle);
        System.out.println(bundle.getString("className"));
    }
}

获取类中的Field

public static void main(String[] args) throws ClassNotFoundException {
    Class studentClass = Class.forName("com.java.bean.Student");
    //获取类中所有public修饰的Field
    Field[] fields = studentClass.getFields();
    System.out.println(fields.length);
    System.out.println(fields[0].getName());
    //获取类中所有的属性
    Field[] fs = studentClass.getDeclaredFields();
    System.out.println(fields.length);
    for (Field field:fs){
        //getModifiers获取修饰符的代号
        System.out.print(field.getModifiers()+"对应的修饰符为");
        System.out.print(Modifier.toString(field.getModifiers())+"--");
        System.out.println(field.getType().getSimpleName()+"--"+field.getName());
    }

通过反射机制反编译Field

        StringBuffer s = new StringBuffer();
//        Class studentClass = Class.forName("com.java.bean.Student");
        Class studentClass = Class.forName("java.lang.String");

//        s.append("public class Student{");
        s.append(Modifier.toString(studentClass.getModifiers())+" class "+studentClass.getSimpleName()+"{");
        Field[] fields = studentClass.getDeclaredFields();
        for (Field field:fields){
            s.append("\n");
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" "+field.getType().getSimpleName()+" "+field.getName());
        }
        s.append("\n}");
        System.out.println(s);

通过一个class文件反编译可以获得一个java源码

反射机制让代码复杂了,但是更灵活了

反射机制的重点两块,一是reflectTest07 通过反射机制设置属性,二是reflectTest10通过反射机制调用方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值