Java基础

Java进阶

阅读源码

1.先了解这个类的所有实用的成员变量

2.了解这个类的构造方法

3.了解这个类的常用方法(方法的来源,分析父类接口中的定义方法)

泛型

广泛的类型(可以为任何类型),简化了代码的书写

<E>表示泛型,括号内可以为任意字母(通常为K、T、V、E),相当于一个占位符,在参数传入后才知道数据的类型

由于Java是一门静态语言,参数类型需要编译时确定

泛型的出现将类型的确定推迟到了代码运行时或者对象生成时

泛型出现就可以在方法中取规定存入的类型,再取出时方法就完成了类型的转换

泛型方法:在方法声明时定义的泛型是只在方法中使用的泛型,不是类上声明的泛型

// public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法
// 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
public <T> List<T> arrayToList(T<> arr)

泛型只能使用引用数据类型,不能使用基本数据类型

泛型只在编译期生效,在运行时泛型会被擦除,在编译时去规定类型,在运行时所有泛型都会变成Object类型,虽然泛型被擦除了,但是编译器会记住泛型(买票看演唱会你进场馆需要检票,进入场馆后就是一个正常人,但是系统有你的票据信息,查看你的信息的还能看到票)

泛型是一种标志,不会影响继承关系,两种泛型不同的对象,也是同一个类的对象

// 是同一个类的对象
Student<Object> s1 = new Student<>();
Student<String> s2 = new Student<>();
// 无界
	SuperArray<?> superArray
// 上界
    SuperArray<? extends Dog> superArray
// 下界
    SuperArray<? super Dog> superArray

集合类

在Java中实现了collection接口的类是集合,集合是一种容器,也就是一种数据结构,用来存储数据

框架中,接口和实现类之间通常存在一个抽象类

集合类常用的实现类:HashMap和ArrayList

集合和数组的区别:

1.数组长度不可变,集合类长度可变

2.数组提供的方法有限,对于添加、删除、插入数据操作非常不方便,并且效率不高

3.数组中存储数据的特点是:有序、可重复的,对于无序、不可重复的需求,不能满足。

4.数组中可以存储基本数据类型,也可以存储引用类型。在集合中只能保存引用类型(保存的是对象的引用地址)

Collection接口

Collection集合是Java提供的一个框架,里面写了很多的类用于使用

Collection就是这个框架的一个顶层接口

Collection定义了一些操作“集合”的方法,是整个集合框架的一个顶层接口,所有的实现类都应该具有这些功能

Collection没有提供get、set方法,有些集合没有这些方法,所以就需要一些子类去定义

List接口

List接口中定义了一些操作集合的一些方法,比如根据索引获取,根据数据修改的方法

List接口的实现类都是默认为有索引的连续结构 数据结构、线性表、链表

List接口中声明的都是一些根据索引(下标)去进行操作的方法,所以List接口的实现类都是可以通过索引的形式去操作的

规定存储的内容,可以存储重复的数据,并且存储结构有序,可以拿到集合中的数据

常用实现类:

ArrayList:底层是基于数组,属于线性表,查询快,增删慢,线程不安全,初始化容量为0,扩容时扩容1.5倍

Vector:和ArrayList相似,线程安全,但一般不使用Vector来实现线程安全,使用CopyOnWriteArrayList来实现,初始化容量为10,扩容时扩容2倍

LinkedList:底层是链表,查询慢,增删快

ArrayList

ArrayList源码

成员变量:int DEFAULT_CAPACITY=10,默认初始化容量

Object[] EMPTY_ELEMENTDATA={}默认的空存储数组

ArrayList的底层存储结构,ArrayList的存储结构是数组

默认是空数组,数组如果扩容就需要占用内容,所以在没有任何元素时,空数组开销最小

Object[] elementData:存储数据的数组,没有进行赋值,在使用时根据容量来进行开辟空间

int size ArrayList的长度

构造器:ArrayList():将空数组赋值给elementData,在没有使用时,赋值的空数组,使用时会重新声明一个长度为10的默认数组

ArrayList(int initialCapacity):存入初始化容量进行数组的初始化

public ArrayList(Collection <? extends E> c):根据所传入的集合进行初始化

<?extends E>:泛型,规定了这个集合指定存储指定的元素

?extends E:存储的元素只能是E或者E的子类

E就是在创建ArrayList所指定的泛型类型,默认为Object类型

elementData = Arrays.copyOf(a,size,Object[].class):将a的所有元素都转化成Object类型的数据,并存储到一个长度为size的数组中

ArrayList是有容量的,如果新增时超过了最大容量,会自动扩容

自动扩容机制

第一步:将原容量扩充1.5倍

第二步:判断扩充过后的容量是否足够,如果不够就让最小容量为最新容量

第三步:判断新容量是否过大,如果过大,最新容量为Inteter.MAX-8

ArrayList相关方法

public class ArrayListDemo {
    public static void main(String[] args) {
        ArrayList<Object> arrayList= new ArrayList<>(10);
        // add 在ArrayList尾部增加数据
        arrayList.add(11);
        arrayList.add("111");
        arrayList.add(true);
        System.out.println(arrayList);
        // get 获取指定下标处的数据
        System.out.println(arrayList.get(1));
        // set 将指定下标处的值替换
        arrayList.set(0,1111);
        System.out.println(arrayList);
        // remove 删除指定下标处的值 把指定位放到最后并设为null,没有改变容量
        arrayList.remove(2);
        System.out.println(arrayList);
        // toArray 转换成一个数组
        Object[] objects = arrayList.toArray();
        // addAll 将另一个ArrayList的所有值增加到指定ArrayList中
        ArrayList<Object> arrayList1 = new ArrayList<>();
        arrayList1.add(222);
        arrayList1.add("2222");
        arrayList.addAll(arrayList1);
        System.out.println(arrayList);
        // subList 输出指定下标之间的数据(前算后不算)
        System.out.println(arrayList.subList(0, 3));
        // removeAll 删除指定的ArrayList
        arrayList.removeAll(arrayList1);
        System.out.println(arrayList);
    }
}

ArrayList常用方法

sort方法

Comparable:比较规则,通常是去规定一个类的自我比较规则

public int compareTo(T o); 自己和o进行比较,返回值为负数,0,整数,分别代表小于,等于,大于

// 定义学生类
//Comparable需要在创建类时就定义比较规则
public class Student implements Comparable<Student>{//继承Comparable接口
    private Integer id;
    private Integer score;

    public Student() {
    }

    public Student(int id, int score) {
        this.id = id;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && score == student.score;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, score);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", score=" + score +
                '}';
    }

    // 实现comparaTo方法
    @Override
    public int compareTo(Student o) {
        return this.getScore() - o.getScore();//从小到大
        //return o.getScore() - this.getScore();//从大到小
    }
}
// Comparable应用
public static void main(String[] args) {
        ArrayList<Student> arrayList = new ArrayList<>();
        arrayList.add(new Student(1, new Random().nextInt(100)));
        arrayList.add(new Student(1, new Random().nextInt(100)));
        arrayList.add(new Student(2, new Random().nextInt(100)));
        arrayList.add(new Student(2, new Random().nextInt(100)));

        Collections.sort(arrayList);
        System.out.println(arrayList);
    }

Comparator:排序规则,每一个实现类就是一种规则

int compare(T o1, T o2); 比较器的一个比较规则

// 定义学生类
public class Student{
    private Integer id;
    private Integer score;

    public Student() {
    }

    public Student(int id, int score) {
        this.id = id;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id && score == student.score;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, score);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", score=" + score +
                '}';
    }
}
// Comparator应用
public static void main(String[] args) {
        ArrayList<Student> arrayList = new ArrayList<>();
        arrayList.add(new Student(1, new Random().nextInt(100)));
        arrayList.add(new Student(1, new Random().nextInt(100)));
        arrayList.add(new Student(2, new Random().nextInt(100)));
        arrayList.add(new Student(2, new Random().nextInt(100)));

        Comparator<? super Student> comparator1 = new Comparator<Student>() {
            @Override
            // 从大到小
            public int compare(Student o1, Student o2) {
                return o1.getScore() > o2.getScore() ? -1 : o1.getScore() == o2.getScore() ? 0 : 1;
                //等价于
                //return o2.getScore() - o1.getScore();
                //return Integer.compare(o2.getScore(), o1.getScore());
            }
        };
        Comparator<? super Student> comparator2 = new Comparator<Student>() {
            @Override
            // 从小到大
            public int compare(Student o1, Student o2) {
                return o1.getId() > o2.getId() ? 1 : o1.getId() == o2.getId() ? 0 : -1;
                //等价于
                //return o1.getId() - o2.getId();
                //return Integer.compare(o1.getId(), o2.getId());
            }
        };
        arrayList.sort(comparator1);
        arrayList.sort(comparator2);
        System.out.println(arrayList);
    }
LinkedList

LinkedList是List的实现类,是AbstarctList抽象类的子类

ArrayList,LinkedList的父类结构是差不多的

ArrayList的底层是线性表结构(一个数组,ArrayList需要扩容的原因),LinkedList的底层是链表结构(一个一个的节点)

区别:

线性表是通过索引下标查询,查找和修改速度快,而插入和删除时需要修改部分数据的下标

链表是通过节点中的头指针或者尾指针去查询下一个元素,插入和删除速度快

LinkedList的难点在于怎么去设计节点

LinkedList源码:

成员变量:Node<E> first:链表的头节点

Node<E> last:链表的尾节点

内部类:Node<E> 链表的节点,里面三个属性分别是前一个节点,后一个节点,和当前节点存储的值

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

尾插:把元素插入到集合的最后

LinkedList增删改查

add();

// 源码
public boolean add(E e) {
        linkLast(e);
        return true;
    }
// 判断当前节点是否是头节点也是尾节点(即插入前链表是否为空)
void linkLast(E e) {// 参数是要插入的元素
        final Node<E> l = last;// 保存插入前的尾节点
        final Node<E> newNode = new Node<>(l, e, null);// 构建存储e的节点,插入到最后,这个节点前就是之前的尾结点,这个节点就是现在的尾结点,next应该为null
        last = newNode;// 将新的节点赋值为尾结点
        if (l == null)// 判断插入前尾节点是否为空,为空表示链表为空,需要将新节点也赋值为头节点
            first = newNode;
        else// 如果不为空,就把之前尾节点的next指向当前节点
            l.next = newNode;
        size++;// 集合长度
        modCount++;
    }

remove();

// 源码
public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                 //判断删除的元素是否存在于链表中
                if (x.item == null) {// 不存在
                    unlink(x);
                    return true;
                }
            }
        } else {// 存在
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
// 删除操作做的其实就是断开前驱节点与后继节点的关系,不为空
E unlink(Node<E> x) {// 当一个节点不被任何一个节点指向时,该节点就不属于这个链表
        // assert x != null;
        final E element = x.item;// 保存当前节点存储的值 作为返回值
        final Node<E> next = x.next;// 当前节点的前节点
        final Node<E> prev = x.prev;// 当前节点的后节点
		// 判断前驱节点是否为空,即删除的节点是否为头节点
        if (prev == null) {// 为空直接将头节点赋值为被删除元素的尾节点
            first = next;
        } else {// 不为空,直接将前驱节点的尾节点赋值为next,即被删除元素的尾节点
            prev.next = next;
            x.prev = null;// 并且断开x与前驱节点的关系
        }
		// 判断后继节点是否为空,即被删除的节点是否是尾节点
        if (next == null) {// 如果是尾节点,那么设置尾节点为被删除节点的前驱节点
            last = prev;
        } else {// 如果不是尾节点,那么直接将尾结点的前驱节点赋值为被删除节点的前驱节点
            next.prev = prev;
            x.next = null;
        }

        x.item = null;// 删除当前节点的元素
        size--;// 长度减一
        modCount++;
        return element;
    }

set();

// 源码
public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
// 判断指定索引处是否在容量内,不在则报异常
private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
// 判断指定索引处是否在容量内
private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
// 找到对应index的节点
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {// 由于LinkedList是双向链表,判断当前要查的节点是在前半段还是在后半段
            Node<E> x = first;// 从头节点开始遍历每个节点的next
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;// 从尾节点开始遍历每个节点的prev
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

get();

// 源码
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
// 判断指定索引处是否在容量内,不在则报异常
private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
// 判断指定索引处是否在容量内
private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
// 找到对应index的节点
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {// 由于LinkedList是双向链表,判断当前要查的节点是在前半段还是在后半段
            Node<E> x = first;// 从头节点开始遍历每个节点的next
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;// 从尾节点开始遍历每个节点的prev
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
Set接口

Collection的一个子接口,和List接口并列

HashSet TreeSet LinkedHashSet

Set集合中存储的是无序的不重复的数据,存储结构无序,无法获取单个元素

HashSet的底层是Hash表结构,存储的元素无序且不重复

当添加了重复的值时,HashSet会自动将其去除

Set或者说Hash表是如何保证元素不重复的?

答:需要重写hashcode和equals方法

Set和List的相互转换
		//将list转换成set
        ArrayList<String> list = new ArrayList<>();
        list.add("富强");
        list.add("民主");
        list.add("文明");

        // 传入list参数
        //同样的方法可以将set转换成list
        HashSet<String> set = new HashSet<>(list);// 可去重,去重最简单的方法
        System.out.println(set);

常用实现类:

HashSet:存储的数据无序,且唯一,底层是一个HashMap

LinkedHashSet:HashSet的子类,存储的数据有序,且唯一,底层是一个LinkedHashMap,每一个节点都有指针去指向下一个节点

TreeSet:把元素进行了排序,TreeSet中存储的元素类必须实现Comparable接口

Map接口

是集合框架的顶层接口,和Collection并列

Map集合是用来存储键值对结构的 key:value结构 即函数映射结构,每个key有且仅有一个对应的value,value可以被多个key映射,所有在Map接口中有一个子接口Entry<K,V>,在Map中存储的就是Entry,每一个Entry都对应了一个键值对

由于一个键只能指向一个值,所以Map中的key不能重复

Map的常用实现类:

HashMap:底层是哈希表+链表+红黑树结构(长度大于8时),存储的是键值对,HashMap的key值可以为null

 		// HashMap的底层是哈希表+链表+红黑树结构
		Map<String,String> map = new HashMap<>();
        // 增
        map.put("001","张三");
        map.put("002","李四");
        map.put("003","王五");
        System.out.println(map);
        // 删
        map.remove("001");
        System.out.println(map);
        // 改
        map.put("002","张三");
        System.out.println(map);
        // 查
        System.out.println(map.get("002"));

        // keyset 输出所有的键
        System.out.println(map.keySet());
        // entryset 输出所有的键值对
        System.out.println(map.entrySet());

LinkedHashMap:在HashMap的基础上给每个节点都添加了前后指针,保证了顺序

HashTable:和HashMap一样,但线程安全,在线程安全问题时,使用ConcurrentHashMap。HashMap是效率最高的,HashTable是效率最低的,HashTable的key值不可以为null。Properties继承于HashTable

TreeMap:把节点用Key进行了排序

当添加键值对时,如果键名重复,就会将之前的键所对应的值所覆盖

HashMap:

内部类:

// Node内部类
// Node<K,V>节点就是用来存储HashMap数据的节点,每一个键值对都对应着一个Node对象
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;// 节点对应的hash值
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

属性:

// 哈希表,用来存储Node节点,是根据Node的HashCode决定存储在哪个索引位置
transient Node<K,V>[] table;

// HashMap存储键值对的结构就是Entry的实现,就是存储所有的Entry
transient Set<Map.Entry<K,V>> entrySet;

// 负载因子,默认为0.75
final float loadFactor;

// 默认容量为16,如果负载因子为0.75,当table存储了12个伪元素后就会进行扩容
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

// 等于容量*负载因子
int threshold;

// 当链表长度超过8时就会树化
static final int TREEIFY_THRESHOLD = 8;

// 当树的节点数小于6时就会链表化
static final int UNTREEIFY_THRESHOLD = 6;

循环遍历

List

for,fori,foreach

public static void main(String[] args) {
        ArrayList<Person> arrayList = new ArrayList<>();
        Random random = new Random();
    	// 添加元素
        for (int i = 0; i < 5; i++) {
            arrayList.add(new Person(random.nextInt(100)));// 生成一个随机的int值,该值介于[0,100)的区间
        }
   		// for
        for (Person person : arrayList) {
            System.out.print(person);
        }
        System.out.println();
    	// fori
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.print(arrayList.get(i));
        }
    	// foreach(通过匿名内部类实现Consumer接口)
    	arrayList.forEach(new Consumer<Person>(){
            @Override
            public void accept(Person p) {
                System.out.println(p);
            }
        });
    	// foreach(通过lambda表达式实现Consumer接口)
    	list.forEach(one->{
            System.out.println(one);
        });
    }
Set

for

public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            set.add(new Person(random.nextInt(100)));
        }
    	// for
        for (Person person : set) {
            System.out.println(person);
        }
    }
Map

entrySet

public static void main(String[] args) {
        HashMap<String, Person> hashMap = new HashMap<>();
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            hashMap.put(i+"",new Person(random.nextInt(100)));
        }
        System.out.println(hashMap);
        // 获取所有键
        System.out.println(hashMap.keySet());
        // 获取所有值
        System.out.println(hashMap.values());
        // 获取所有键值对
        Set<Map.Entry<String,Person>> entries = hashMap.entrySet();
        System.out.println(entries);
        // entrySet遍历Map
        for (Map.Entry<String,Person> entry: hashMap.entrySet()) {
            System.out.println(entry);
        }
    }
迭代器

ArrayList和LinkedList所独有的

public static void main(String[] args) {
        ArrayList<Person> arrayList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            arrayList.add(new Person(random.nextInt(100)));
        }
    	// 创建迭代器
        Iterator<Person> iterator = arrayList.iterator();
    	// 迭代器遍历
        while (iterator.hasNext()) {
            Person next = iterator.next();
            System.out.println(next);
        }
    }

集合工具类Collections

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(3);
        arrayList.add(2);
        ArrayList<Integer> arrayListA = new ArrayList<>();
        arrayListA.add(6);
        arrayListA.add(8);
        arrayListA.add(7);
        System.out.println(arrayList);
        System.out.println(arrayListA);

        // sort方法 对元素进行排序
        Collections.sort(arrayList);
        System.out.println("sort:"+arrayList);

        // shuffle方法 打乱当前顺序
        Collections.shuffle(arrayList);
        System.out.println("shuffle:"+arrayList);

        // copy方法 用后者替换前者
        Collections.copy(arrayListA,arrayList);
        System.out.println("copy:"+arrayListA);

        // addAll方法 添加元素到原集合中
        Collections.addAll(arrayList,1,2,3,4,5);
        System.out.println("addAll:"+arrayList);

        // reverse方法 反转
        Collections.reverse(arrayList);
        System.out.println("reverse:"+arrayList);
    }

枚举类

使用enum来声明类

枚举类就是一个对象数量有限的类,并且对象都在类中定义好

public enum Sesson {
    SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天");
    String name;

    Sesson(String name) {
        this.name = name;
    }
}

枚举类无法继承其他类,只能继承Enum类型的类

例:

public class ResultInfo<T> {
    Integer code;
    String message;
    T data;

    ResultInfo() {
    }

    ResultInfo(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public ResultInfo(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Integer getState() {
        return code;
    }

    public void setState(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    // 定义一个枚举类,其中状态码只有成功和失败
    enum StateCode {
        FAILED(500),SUCCESS(200);
        Integer code;

        StateCode(Integer code) {
            this.code = code;
        }
    }

    @Override
    public String toString() {
        return "ResultInfo{" +
                "code='" + code + '\'' +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}
// 测试
public static void main(String[] args) {
        System.out.println(new ResultInfo<String>(ResultInfo.StateCode.SUCCESS.code, "成功", "数据"));
    }

常用方法

异常

异常出现时,程序就结束,异常后的语句就不会执行

令异常不发生:修改代码,检测参数

使异常发生后继续执行:发生异常后捕获并处理异常

Throwable类是java中所有异常和错误的超类

Error(错误):严重问题,会导致JVM虚拟机崩溃或是代码无法运行,一般无法捕获处理,需要修改代码或修改环境

exception(异常):在代码运行的时候发生的不正常情况

1.受检异常:这种异常必须在编译时就进行捕获

2.非受检异常:这种异常通常可以通过修改代码或逻辑去避免的异常

3.自定义异常:写一个检查性异常类,则需要继承 Exception 类;写一个运行时异常类,那 么需要继承 RuntimeException 类

//自定义一个异常类
public class SjException extends Exception{
    //无参构造
    public SjException() {
    }
    //有参构造,并且使用super调用父类的构造方法,传递异常信息
    public SjException(String message) {
        super(message);
    }
}
//自定义异常类:三角形的判断和面积
//自定义类Sanj,其中有成员 x,y,z,作为三边长,构造方法Sanj(x,y,z)分别 给x,y,z赋值
//方法求面积getArea和显示三角形信息(三个边长)showInfo
//这2个方法中当三条边不能构成一个三角形时要抛出自定义异常SjException,否则显示正确信息
//在另外一个类中的主方法中构造一个Sanj对象(三边为命令行输入的三个整数),显示三角形信息和面积,要求捕获异常

// Sanj类
public class Sanj {
    private int x;
    private int y;
    private int z;

    public Sanj() {
    }

    public Sanj(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public void getArea() throws SjException {
        if (x + y > z && x + z > y && y + z > x) {
            double p = (double) (x + y + z) / 2;
            double a = Math.pow(p * (p - x) * (p - y) * (p - z), 1 / 2);
            System.out.println("三角形的面积为:"+a);
        } else {
            throw new SjException("不能构成三角形");
        }
    }

    public void showInfo() throws SjException {
        if (x + y > z && x + z > y && y + z > x) {
            System.out.println("三角形的三边分别为:"+x+","+y+","+z);
        } else {
            throw new SjException("不能构成三角形");
        }
    }
}
// 测试类
public class TriangleAreaTest {
    public static void main(String[] args) {
        Sanj sanj = new Sanj(2,2,3);
        try {
            sanj.getArea();
            sanj.showInfo();
        } catch (SjException e) {
            e.printStackTrace();
        }
    }
}

try...catch

catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会

被检查。如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方

法是一样。

在使用try...catch时,若第一个catch中写入的是(Exception e),则该catch后的再写入其他的catch则会发生编译错误

try {
// 程序代码
} catch(ExceptionName e1) {
//Catch 块
}

finall

finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally代码块中的代码总会被执行。

try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}

try...catch...finally

public static int test(){
//try语句块中有 return 语句时,会先执行try,然后将值保存在i中用于return,然后再执行finally
	int i = 1;
	try{
		i++;
		System.out.println("try block, i = "+i);
		return i;
	}catch(Exception e){
		i ++;
		System.out.println("catch block i = "+i);
		return i;
	}finally{
		i = 10;
		System.out.println("finally block i = "+i);
	}
}

// 输出
try block, i = 2
finally block i = 10
2
public static int test(){
//finally语句块中有 return 语句时,最终会采用 finally 代码块中的 return 语句进行返回,而直接忽略 try 语句块中的
return 指令
	int i = 1;
	try{
		i++;
		System.out.println("try block, i = "+i);
		return i;
	}catch(Exception e){
		i++;
		System.out.println("catch block i = "+i);
		return i;
	}finally{
		i++;
		System.out.println("finally block i = "+i);
		return i;
	}
}

// 输出
try block, i = 2
finally block i = 3
3

线程

并发:两个或多个事件在同一时间段内发生,一个cpu去执行多件事情

并行:两个或多个事件在同一时刻发生, 多个cpu去执行多件事情

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运

行多个线程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个

进程从创建、运行到消亡的过程。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个

进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

程序是在所有线程结束后才结束

多线程:在一个进程中,用多个线程去执行任务,只占用一个cpu

JVM只是一个进程,只会占用一个cpu

当只有一个任务时,不管是单线程还是多线程,都只是使用了一个cpu,也就是总体效率不变,多线程没有提高速度,但由于多线程之间还要抢占cpu执行权,并且要进行通信,所以多线程比单线程慢

java程序启动至少启动两个线程

1.main线程

2.gc线程 垃圾回收

多线程在宏观角度就是多个事情在同时发生

多线程在微观角度就是main线程和多线程的其他线程在抢cpu的执行权,每一个执行权就是一个时间片

创建线程:

一.继承Thread类

1.继承Thread类

2.重写run方法,run方法中的内容,就是这个线程要去执行的任务

3.创建子类对象调用start方法,启动run任务

二.实现Runnable接口

1.实现Runnable接口

2.实现run方法,run方法中的内容就是这个线程要去执行的人物

3.创建子类对象,通过子类对象创建Thread类对象

4.调用Thread类对象的start()方法

Runnable方式创建相对于Thread方式创建的好处:

适合多个相同的程序代码的线程去共享同一个资源,多个线程去操作一个对象。可以避免java中的单继承的局限性。

增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。

三.匿名内部类创建

	    // 新增线程记录时间
        // 超时挑战失败
        new Thread(new Runnable() {// 匿名内部类
            @Override
            public void run() {
                try {
                    Thread.sleep(10*1000);// 睡眠10秒,10秒后触发
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("挑战失败");
                System.exit(0);
            }
        }).start();

		//lambda表达式实现匿名内部类以创建线程的方式
		new Thread(()->{
            System.out.println("线程开启");
        }).start;

四.实现Callable接口(这个线程是有返回值的)

1.实现Callable接口

2.创建实现类对象,通过实现类对象创建FutureTask对象

3.通过FutureTask对象生成Thread对象

4.调用Thread对象的start()方法启动线程

5.调用FutureTask对象的get()方法获取返回值

get()方法是一个阻塞方法

阻塞方法:会阻塞线程,直到获取到值为止(让自身线程执行完毕)

五.线程池创建

使用Executors线程池工具类,通过提供的静态方法创建线程池

线程池是用来存放线程的,让线程可以重复使用

1.创建线程池

2.创建线程

3.把线程放入线程池中,并执行

public class ThreadPool {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        // 创建线程
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(i);
                    }
                }
            }
        };
        // 将线程放入线程池
        // 开启三个线程
        pool.submit(runnable);
        pool.submit(runnable);
        pool.submit(runnable);
    }
}

线程的启动就是调用start方法,start方法会调用当前Thread对象的run()方法

使用Runnable接口的形式创建的线程会传入target属性,这个就是被调用run方法的线程对象

Thread的run方法,会判断是否执行target的run方法

Thread常用方法

静态方法

1.currentThread():获取当前正在运行的线程

2.yield():让步,让出当前的执行权。让步后,当前线程依然可以抢执行权

		if (i%3 == 0) { // 当i为3或3的倍数时,让权
            thread.yield();
        }

3.sleep(long mills):让当前进程进入睡眠,睡眠时间为mills毫秒,睡眠时是进入了阻塞状态,时间结束后进入就绪状态,等待cpu的调用

		if (i%3 == 0) { // 当i为3或3的倍数时,等待1秒
            thread.sleep(1000);
        }

普通方法

1.start():启动当前线程

2.getName():获取当前线程的名字

3.setName():设置当前线程的名字

4.interrupt():发起中断请求,请求中断当前进程,只要当前进程进入阻塞状态就会中断

5.isAlive():检测当前线程是否存活(只有当一个线程全部执行完后才会死亡),返回的是一个boolean值

// 求1到100000之间的质数,判断多线程的时长
long begin = System。currentTimeMillis();
long end = 0;
Thread thread1 = new Thread(new MyThread(0,20000));
Thread thread2 = new Thread(new MyThread(20000,40000));
Thread thread3 = new Thread(new MyThread(40000,60000));
Thread thread4 = new Thread(new MyThread(60000,80000));
Thread thread5 = new Thread(new MyThread(80000,100000));
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
while (true) {
    if (!thread1.isAlive() && !thread2.isAlive() && !thread3.isAlive() && !thread4.isAlive() && !thread5.isAlive()) {
        end = System.currentTimeMillis();
        System.out.println(end-begin);
        break;
    }
}

// MyThread类
Integer begin;
Integer end;
    
(构造方法)
    
public void run() {
        for (int i = begin; i < end; i++) {
            boolean flag = true;
            for (int j = 2; j < i; j++) {
                if (i%j == 0) {
                    flag = false;
                }
            }
            if (flag) {
                System.out.println(i);
            }
        }
    }

6.setProority():设置优先级,10最高,1最低,默认为5(值越高,抢占概率越大)

7.join():等待当前线程死亡,即等待当前线程执行完,会使调用这个方法的线程进入阻塞状态

mt.join(); // “当前线程”指的是该方法调用写在哪个线程中,若写在main中,则是等待main线程执行完后执行mt线程

8.setDaemon():设置当前线程是否为守护线程

用户线程:独立线程,主线程结束之后,当前线程依然会继续执行直到结束

守护线程:如果当前线程是某一个线程的守护线程,会随着那个线程的结束而结束

9.getState():获取当前线程的状态

线程安全问题:数据共享时不同步。

解决:

1.使用同步代码块synchronized (o),代码块中的内容只能有一个线程进入,获取到o这个同步锁(可以使用任意一个对象来作为同步锁)的线程进入,没有获取到锁的线程会进入阻塞状态,直到获取锁后再运行。sleep方法会进入阻塞状态,但不会释放锁;wait方法进入阻塞状态后,会释放锁

(有同步锁时才能使用下列方法)

wait方法:

使当前线程进入阻塞态,并释放锁

wait方法是Object提供的方法

wait方法可以不填时间,表示无限等待

notify方法:

notify方法是Object提供的方法

随机唤醒一个wait的当前对象

notifyAll方法:

唤醒所有的wait对象

public class PrintTest {
    public static void main(String[] args) {
        Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                        for (int i = 0; i < 27; i++) {
                            System.out.println(Thread.currentThread().getName()+":"+i);
                            try {
                                lock.notify();// 不要先睡后唤醒,否则会“睡死”
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    System.exit(0);
                    }
                }
        }, "数字线程").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                        for (int i = 0; i < 26; i++) {
                            char c = (char) ((char) i + 'A');
                            System.out.println(Thread.currentThread().getName()+":"+c);
                            try {
                                lock.notify();// 唤醒打印数字的线程
                                lock.wait();// 等待数字被打印完
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    System.exit(0);
                    }
                }
        },"字母线程").start();
    }
}

2.使用同步方法public synchronized void 方法名(){ }

public class Ticket implements Runnable{
	private int ticket = 100;
	/*
	* 执行卖票操作
	*/
	@Override
	public void run() {
		//每个窗口卖票的操作
		//窗口 永远开启
		while(true){
			sellTicket();
		}
	}
	/*
	* 锁对象 是 谁调用这个方法 就是谁
	* 隐含 锁对象 就是 this
	*
	*/
	public synchronized void sellTicket(){
		if(ticket>0){//有票 可以卖
		//出票操作
		//使用sleep模拟一下出票时间
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//获取当前线程对象的名字
			System.out.println(Thread.currentThread().getName()+"正在
			卖:"+ticket‐‐);
		}
	}
}

3.使用lock锁,首先在成员位置创建一个ReentrantLock对象,然后,在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁最后,在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

public class Ticket implements Runnable{
	private int ticket = 100;
	Lock lock = new ReentrantLock();
	/*
	* 执行卖票操作
	*/
	@Override
	public void run() {
		//每个窗口卖票的操作
		//窗口 永远开启
		while(true){
             // 上锁
			lock.lock();
			if(ticket>0){//有票 可以卖
			//出票操作
			//使用sleep模拟一下出票时间
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			//获取当前线程对象的名字
		String name = Thread.currentThread().getName();
		System.out.println(name+"正在卖:"+ticket‐‐);
		}
         // 解锁
		lock.unlock();
		}
	}
}

死锁

必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释

放。

3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这

样就形成了一个等待环路。

(重要)线程状态:

1.出生:创建,new,线程对象被创建时处于这个状态

2.就绪:线程对象调用start方法时的状态,可以被执行态

3.运行:cpu调度当前进程

4.阻塞:因为获取不到锁,或者调用sleep,wait,join等方法进入等待状态

5.销毁:线程执行完成任务,自我毁灭

IO流

输入和输出

输入:从硬盘中把数据读取到程序中

输出:从程序中把数据写入到硬盘中

文件和文件夹用于存储各种各样的数据,在Java中,文件也是一个对象,而File类中就有操作文件的方法

在Windows系统中,每一层级用反斜杠 \ 表示(在Java中,由于反斜杠是有特殊含义的,所以需要使用反斜杠将反斜杠转译成无意义的字符,即用两个反斜杠);Linux系统中,用斜杠 / 表示

如果指定路径没有对应文件,同样也会生成一个File对象

绝对路径:这个文件所在位置的整体盘符路径

相对路径:这个文件从当前文件开始的路径

File类的构造器:

File(String path):使用绝对路径或者相对路径找到文件

File(String parent,String child):父路径和子路径创建

File(File parent,String child):父类文件夹和子路径创建

	    File file = new File("D:\\img\\123.txt");
        System.out.println(file.length());
        File file1 = new File("second_stage\\io\\src\\com\\moon\\123.txt");// 左上角路径
        System.out.println(file1.length());
        File file2 = new File("D:\\img");
        System.out.println(file2.isDirectory());
        File file3 = new File(file2, "123.txt");
        System.out.println(file3.getName());
        File file4 = new File("123.txt", "123.txt");
        System.out.println(file4.getName());

File常用方法:

获取功能的方法:

public String getAbsolutePath() :返回此File的绝对路径名字符串。

public String getPath() :将此File转换为路径名字符串。

public String getName() :返回由此File表示的文件或目录的名称。

public String getParent():获取上层文件目录路径。若无,返回null

public long length() :返回由此File表示的文件的长度。

public long lastModified():获取最后一次的修改时间,毫秒值

public String[] list():获取指定目录下的所有文件夹或者文件目录的名称数组

public File[] ListFiles():获取指定目录下的所有文件或者文件目录的File数组

判断功能的方法

public boolean exists():此File表示的文件或目录是否实际存在

public boolean isDirectory():此File表示的是否为目录

public boolean isFile():此File表示的是否为文件

创建删除功能的方法

public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

public boolean delete() :删除由此File表示的文件或目录。

public boolean mkdir() :创建由此File表示的目录。

public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

File f = new File("aaa.txt");
System.out.println("是否存在:" + f.exists());
System.out.println("是否创建:" + f.createNewFile());
System.out.println("是否存在:" + f.exists());
// 目录的创建
File f2 = new File("d:\\aaa");
System.out.println("是否存在:" + f2.exists());
System.out.println("是否创建:" + f2.mkdir());
System.out.println("是否存在:" + f2.exists());
// 创建多级目录
File f3 = new File("d:\\workspace");
System.out.println(f3.mkdir());
File f4 = new File("d:\\workspace");
System.out.println(f4.mkdirs());
// 文件的删除
System.out.println(f.delete());
// 目录的删除
System.out.println(f2.delete());
System.out.println(f4.delete());

// 输出
是否存在:false
是否创建:true
是否存在:true
是否存在:false
是否创建:true
是否存在:true
true
false
true
true
true

目录的遍历

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

File file6 = new File("D:\\img");
String[] name = file6.list();
for (String s : name) {
    System.out.println(s);
}

File[] files = file6.listFiles();
for (File file7 : files) {
    System.out.println(file7);
}

// 输出
123.txt
456.txt
789.txt
D:\img\123.txt
D:\img\456.txt
D:\img\789.txt

递归思想

//递归调用方法
//自己调用自己的逻辑去处理斐波那契数列
//1 1 2 3 5 813213455定义方法获取当前位置的数是多少
//第10位我就需要直到第8位和第9位,我可以再次去调用我自己这个逻辑,直到去找到第一位和第二位的值,由于直到第一位和第二位的值为1 所以递归一定要具有一个终点不然程序就无法结束会导致栈溢出

	public static void main(String[] args) {
        System.out.println(getI(10));
    }
    public static int getI(int i) {
        if (i == 1 || i == 2) {
            return 1;
        }
        return getI(i-1)+getI(i-2);
    }

递归遍历文件

public class DiGuiDemo2 {
        public static void main(String[] args) {
// 创建File对象
            File dir = new File("D:\\workspace");
// 调用打印目录方法
            printDir(dir);
        }
    
        public static void printDir(File dir) {
// 获取子文件和目录
            File[] files = dir.listFiles();
// 循环打印
            for (File file : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
                if (file.isFile()) {
// file是文件,输出文件绝对路径
                    System.out.println("文件名:"+ file.getAbsolutePath());
                } else {
// 是目录,输出目录绝对路径
                    System.out.println("目录:"+file.getAbsolutePath());
// 继续遍历,调用printDir,形成递归
                    printDir(file);
                }
            }
        }
    }

文件过滤器 6/20/20:34

FileFilter接口

实现accept方法,接收参数为file的对象,如果保留这个对象,则返回true,过滤掉的就返回false

// 代码
//过滤器FileFilter的实现类:
    public class FileFilterImpl implements FileFilter{
        @Override
        public boolean accept(File pathname) {
//如果pathname是一个文件夹,返回true,继续遍历这个文件夹
            if(pathname.isDirectory()){
                return true;
            }
            return pathname.getName().toLowerCase().endsWith(".java");
        }
    }
// 测试类1
// 在遍历的基础上添加一个过滤器,本例的过滤器作用是过滤掉不是以java结尾的文件
public class Demo01Filter {
        public static void main(String[] args) {
            File file = new File("D:\\workspace");
            getAllFile(file);
        }
        public static void getAllFile(File dir){
            File[] files = dir.listFiles(new FileFilterImpl());//传递过滤器对象
            for (File f : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
                if(f.isDirectory()){
//f是一个文件夹,则继续遍历这个文件夹
                    getAllFile(f);
                }else{
//f是一个文件,直接打印即可
                    System.out.println(f);
                }
            }
        }
    }
// 测试类2
public class Demo02Filter {
        public static void main(String[] args) {
            File file = new File("D:\\workspace");
            getAllFile(file);
        }
        public static void getAllFile(File dir){
//传递过滤器对象 使用匿名内部类
            File[] files = dir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
//过滤规则,pathname是文件夹或者是.java结尾的文件返回true
                    return pathname.isDirectory() ||
                            pathname.getName().toLowerCase().endsWith(".java");// 保留以java结尾的文件
                }
            });
            for (File f : files) {
//对遍历得到的File对象f进行判断,判断是否是文件夹
                if(f.isDirectory()){
//f是一个文件夹,则继续遍历这个文件夹
                    getAllFile(f);
                }else{
//f是一个文件,直接打印即可
                    System.out.println(f);
                }
            }
        }
    }

IO:

按流向分类:

1.输入流:数据从硬盘流向代码内存

2.输出流:数据从代码内存流向硬盘

按数据类型分类

1.字节流(byte):以字节为单位,读写数据的流

2.字符流(char):以字符为单位,读写数据的流

四大顶级父类

InputStream:定义了一些读取操作

OutputStream:定义了一些写入操作

图片和视频使用字节流操作

读写文件的流

FileInputStream:InputStream的子类,用于从文件中读取字节

public static void fisDemo() throws IOException {
        // 所读的文件必须要存在,若不存在,则会产生异常
        FileInputStream fis = new FileInputStream("second_stage\\io\\abc.txt");
        // 读出来的是int值,因为读取的时候是一个字节一个字节读取的
        char read = (char)fis.read();
        System.out.println(read);

    	// 第一种
        // 循环读取文件内容,读到文件末尾返回-1
        int b;
        while ((b = fis.read()) != -1) {
            // 打印一次后,跳过n位不读
            fis.skip(2);
            System.out.print((char)b);
        }

    	// 第二种
        // 最快读取文件的方法
        byte[] c = new byte[1024];
        fis.read(c);// 一次读取1024个字节
        String s = new String(c);// 以字符串形式输出
        System.out.println(s);
        fis.close();
    }

FileOutputStream:OutputStream的子类,用于将数据写入到文件

public static void fosDemo() throws IOException {
        FileOutputStream fos;
        // 创建文件输出流时,需要使用一个File对象来创建,如果当前文件不存在,就会创建一个新文件,如果存在,就会用新文件覆盖
        // 如果File对象是一个文件夹的话就会报异常
        fos = new FileOutputStream("second_stage\\io\\abc.txt",true);// 加上true后则不会覆盖,而是追加写入
        // 一个字符占用两个字节,但字母只占用一个字节,汉字占用两个字节
        // 写入一个字节
        fos.write(97);
        // 一次写入一个byte数组
        // 写入中文时,不建议使用字节流
        fos.write("国".getBytes());
        // 从byte的第off位写到len位
        fos.write("HelloWorld".getBytes(),1,4);
        // 用完流之后要关闭
        fos.close();
    }

FileReader:字符读取流,和字节流基本一样,但字符流每次读出也都是以char为单位,一个字符一个字符的读出

public static void frDemo() throws IOException {
    	FileReader fr = new FileReader("second_stage\\io\\abc.txt");
    	// 第一种
    	// 循环遍历
    	int read = 0;
        while ((read = fr.read()) != -1) {
            System.out.print((char)read);
        }
    
    	// 第二种
    	char[] c = new char[1024];
        fr.read(c);
        System.out.println(s);
    
        fis.close();
}

// 读取文件,并判断文件中“花”字出现的次数
public static void main(String[] args) {
        File file = new File("second_stage\\src\\test\\text.txt");
        try {
            FileReader reader = new FileReader(file);
            BufferedReader bufferedReader = new BufferedReader(reader);
            int count = 0;
            String temp = "";
            //一行一行读取内容,返回字符串
            while ((temp = bufferedReader.readLine())!=null) {
                //将字符串转换成char数组
                char[] chars = temp.toCharArray();
                // 遍历char数组
                for (char aChar : chars) {
                    if (aChar == '花') {
                        count++;
                    }
                }
            }
            System.out.println(count);
            reader.close();
            bufferedReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

FileWriter:字符输入流,用法和字节流基本一样。但字符流每次写入都是以char为单位,不会出现字符写一半,字符流可以直接写入String字符串。必须关闭流,否则不会写入,因为存在一个缓冲区(减少IO流的频率,增加文件的读写效率),也可以使用flush方法,将缓冲区的内容写入文件

IO次数:按一个字节一个字节的复制文件时,假如一张图片大小为1024字节,读取时,需要1024次读完,再通过1024次将图片写到文件夹中,整个操作的IO次数为2048次

缓冲流

提供了一个能够读取大量字节数据的容器,因此读写次数减少,IO次数减少,效率提升

字节缓冲流:

输入流:BufferedInputStream

使用:new BufferedInputStream(InputStream in) 传入一个InputStream类型的变量

输出流:BufferedOutputStream

使用:new BufferedOutputStream(OutputStream in) 传入一个OutputStream类型的变量

// 从一个文件夹中复制一张图片到另外一个文件夹中。 图片只能用字节流 图片的底层是二进制
public static void imgTest1() throws IOException {
        FileInputStream fis = new FileInputStream("second_stage\\io\\imgA\\agt.jpg");
        FileOutputStream fos = new FileOutputStream("second_stage\\io\\imgB\\agt.jpg");

        // 创建缓冲流
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        int data;
        // 将所读文件存入到data中,然后写入
        while ((data = bis.read()) != -1) {
            bos.write(data);
        }

        bis.close();
        bos.close();
    }

字符缓冲流

序列化流

序列化操作(写):对象-->字节-->存储到文件中

new ObjectOutputStream(OutputStream out) 传入一个OutputStream类型的变量

public static void main(String[] args) throws IOException {
        Student student = new Student("张三",18);// 创建的对象类需要实现Serializable接口
        ObjectOutputStream ops = new ObjectOutputStream(new 					FileOutputStream("second_stage\\io\\imgA\\123.txt"));
        ops.writeObject(student);
        ops.close();
    }

反序列化操作(读):文件-->对象

new ObjectInputStream(InputStream out) 传入一个InputStream类型的变量

public static void read() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("second_stage\\io\\imgA\\123.txt"));
        Student student = (Student) ois.readObject();
        System.out.println(student);
        ois.close();
    }

将一个对象成功序列化后,如果将类信息改变,此时无法反序列,因为实现Serializable接口后,在每次对象序列化时,提供一个序列化id(serialVersionUID),在改变类后,序列号会重新提供,此时反序列化时,会导致序列号不匹配

解决:在类中手动声明一个id,public static final long serialVersionUID = 12345;再重新序列化后,就可以使用

Redis数据库:以key:value存储数据,(序列化操作)存储到本地文件中(可存储HashMap,List,Object)

反射

一个类需要加载编译后才可执行,即将类封装成一个Class对象,并将类的各部分(成员变量,构造方法,成员方法)进行存储

传统:new 对象——>获取类的信息

反射:拿到类的Class对象——>获取类的所有信息

创建类的Class对象:

1.Class.forName(类的路径);(常用)

// 反射必须要创建Class对象,可用于获取成员变量,构造方法和成员方法,当需要给成员变量赋值和调用方法时,就需要利用Class对象创建具体对象
Class people = Class.forName("People");
// newInstance() 利用Class对象创建People对象
People o = (People)people.newInstance();

// 根据反射获取类中的成员变量
people.getField(String name);// 根据名字获取public修饰的变量
people.getFields();// 返回类中public修饰的成员变量
people.getDeclaredField(String name);// 根据名字获取变量
people.getDeclaredField();// 返回类中所有成员变量
Field[] declaredFields = people.getDeclaredFields();// 获取类中所有的成员变量
for (Field declaredField : declaredFields) {
    System.out.println(declaredField.getName());
}

// 使用反射给People对象的成员变量赋值
People p = (People) people.newInstance();
//给对象的name赋值,(1)获取到name变量(2)给指定对象的name赋值
//暴力反射,用以给私有变量赋值
name.setAccessible(true);
// set(对象,值)
name.set(p,"张三");// 表示给p对象的name赋张三
// get(对象)
System.out.println(name.get(p));// 获取

// 使用反射获取所有的构造方法
Constructor[] constructor = people.getConstructors();
for (Constructor constructor1 : constructor) {
    System.out.println(constructor1);
}
// 使用无参构造创建对象
// (1)获取到无参构造(2)调用newInstance方法创建对象
Constructor constructor1 = people.getConstructor();
People o = (People)constructor1.newInstance();
// 使用有参构造创建对象
// (1)获取到有参构造(2)调用newInstance方法创建对象
Constructor constructor2 = people.getConstructor(String.class, int.class, char.class);
constructor2.newInstance("张三",11,'男');

// 获取成员方法
//无参
Method m = people.getMethod("eat");
// invoke(对象) 执行方法,通过指定对象调用方法
m.invoke(o);
// 有参
Method m2 = people.getMethod("eat",String.class);
m2.invoke(o,"肉");

2.类名.class

Class people1 = People.class;

3.对象.getClass(); (不常用)

Class people2 = new People().getClass();

Spring框架(反射+设计模式):

IOC:管理对象的容器

配置文件中写入类的路径,通过IO读取文件,拥有类的路径后就可以通过反射创建对象,再存储 到HashMap中,类名为key,对象为值,再通过getBean拿到对象

IOCTest类

//模拟IOC
//1.创建一个文本文件,编写要创建的对象的类路径
//2.在IOCTest类中,通过IO流读取文件中的信息(字符缓冲流)
//3.通过反射创建类的对象
//4.将对象存储到HashMap中,以类名为key,以对象为value进行存储
public class IOCTest {
    // 存储对象的map
    static HashMap<String,Object> map = new HashMap<String, Object>();

    // 静态代码块,类加载时运行,在类加载时就把初始化工作完成
    static {
        try {
            // 创建了一个字符缓冲输入流
            BufferedReader bufferedReader = new BufferedReader(new FileReader("second_stage\\reflect\\src\\moon\\IOCTest.txt"));
            // 读取文件中的内容
            String path = "";
            while ((path = bufferedReader.readLine()) != null) {// readLine(); 一行一行地读
                // 通过反射创建对象
                Class<?> c = Class.forName(path);// Class字节码对象
                Object obj = c.newInstance();// 创对象
                // 以类名作为key,对象为value存储到map中
                //path.substring(path.lastIndexOf(".") + 1 因为路径最后一个点后面就是类名,所以用subString进行截取
                map.put(path.substring(path.lastIndexOf(".") + 1), obj);//subString(start,end)对字符串进行截取  lastIndexOf(str)获取指定字符串最后一次出现下标
            }
        } catch (Exception e) {
                e.printStackTrace();
            }
    }

    // 编写一个通过类名获取对象的方法
    public static Object getBean(String name) {
        if (map.get(name) == null) {
            System.out.println("没有该对象");
            return null;
        }
        return map.get(name);
    }
}

文本文件(类的路径)

moon.People

测试类

public static void main(String[] args) {
        //获取People对象
        People p = (People) IOCTest.getBean("People");
        System.out.println(p);
    }

注解

也称为元数据,和类,接口,枚举在同一层次

可以将注解声明在包,类,变量,方法上,用于对这些元素进行说明

常见注解

1.@Override:用于方法上,表示该方法是重写父类的方法

2.@Deprecated:用于方法上,表示该方法已经过时,但可以继续使用

3.@SuppressWarning("all"):用于类上,表示取消所有代码警告

自定义注解

属性:注解中的属性为抽象方法,抽象方法的返回类型可以是(1)基本数据类型(2)String(3)枚举(4)数组

//自定义注解
public @interface AnnoDemo {
    // 返回值为int
    int age();
    
    // 返回值为String
    String name();
    
    // 返回值为数组
    String[] hobbies();
}

// 使用
@AnnoDemo(age = 18,name = "张三",hobbies = {"唱","跳"})



// 设置默认值后,在使用时可以不赋值
public @interface AnnoDemo {
    // 返回值为int
    int age() default 0;

    // 返回值为String
    String name() default "";

    // 返回值为数组
    String[] hobbies() default {};
}

// 使用
@AnnoDemo()

1.用于类上的注解

//作用于类上的注解
@Target(value = ElementType.TYPE)// 表示该注解作用于类上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface ClassAnno {
    
}

2.用于方法上的注解

//作用于方法上的注解
@Target(value = ElementType.METHOD)// 表示该注解作用于方法上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface MethodAnno {
    
}

3.用于变量上的注解

//作用于变量上的注解
@Target(value = ElementType.FIELD)// 表示该注解作用于变量上
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface FieldAnno {
    
}

4.多种作用域

//作用于变量,方法,类上的注解
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)// 表示在运行时加载该注解,否则会报空指针异常(自定义注解必须加)
public @interface AllAnno {
    
}

获取注解上的属性值(反射)

类上

//注解
@AnnoDemo(age = 18,name = "张三",hobbies = {"唱","跳"})
public class NoteTest {
}

//获取类注解上的属性值
public class NoteTest {	
	public static void main(String[] args) {
        	// 1.创建类上字节码Class对象
        	Class<NoteTest> c = NoteTest.class;
        	// 2.获取类上注解
        	AnnoDemo annotation = c.getAnnotation(AnnoDemo.class);
        	// 3.获取属性
        	System.out.println(annotation.name());
    	}
}

变量上

//注解
public class NoteTest {	
    @FieldAnno(type = true)
    int i;
}

//获取类中变量注解上的属性值
public class NoteTest {	
	public static void main(String[] args) {
    		// 1.创建类上字节码Class对象
        	Class<NoteTest> c = NoteTest.class;
        	// 2.通过字节码对象获取到变量
        	Field f = c.getDeclaredField("i");
        	// 3.获取变量上注解
        	FieldAnno annotation = f.getAnnotation(FieleAnno.class);
        	// 4.获取属性
        	System.out.println(annotation.type());
    }
}

方法上

//注解
@MethodAnno(times = 10)
public static void test() {
    
}

//获取类中方法注解上的属性值
public class MethodAnnoTest {
    public static void main(String[] args) throws NoSuchMethodException {
        // 1.创建类上字节码Class对象
        Class<MethodAnnoTest> c = MethodAnnoTest.class;
        // 2.通过字节码对象获取到方法
        Method m = c.getMethod("test");
        // 3.获取方法上注解
        MethodAnno methodAnno = m.getAnnotation(MethodAnno.class);
        // 4.获取属性
        System.out.println(methodAnno.times());
    }
}

网络编程

是指多台设备之间,通过计算机网络进行连接并进行数据传输

网络编程三要素:

(1)协议:设备之间通过网络进行通讯时遵守的规则

1.TCP(在线发送):规定了设备之间必须连接过后才能传输数据

2.UDP(离线发送):设备之间不需要进行连接就能传输数据。是一个不可靠协议

(2)IP地址:在同一网络上,作为设备的唯一标识(手机有电话号码)

(3)端口号:作为设备中的应用程序运行的唯一标识

其他设备可通过 IP:端口号 对程序进行访问

Java实现TCP程序

包含服务端和客户端

步骤:

(1)服务端先启动,等待客户端连接

(2)客户端需要通过IP和端口号连接服务端

服务端的实现:

ServerSocket类:实现了服务端的套接字

// 客户端显示文字
public class Server {
    public static void main(String[] args) {
        // 需要在finally中关闭,所以需要在外面声明
        ServerSocket serverSocket = null;
        Socket socket = null;
        OutputStream out = null;
        try {
            //1.创建了服务端套接字对象,并指定端口号为8081
            serverSocket = new ServerSocket(8081);
            //2.accept(); 等待接收客户端连接
            socket = serverSocket.accept();
            System.out.println("有客户端连接了");
            //3.客户端连接服务端后,服务端响应数据给客户端
            //(1).通过socket获取输出流
            out = socket.getOutputStream();
            //(2).响应给浏览器的固定设置
            out.write("HTTP/1.1 200 OK\r\n".getBytes());
            out.write("Content-Type:text/html\r\n".getBytes());
            out.write("\r\n".getBytes());
            //(3).往浏览器写数据
            out.write("<h1 style='color:red'>hello</h1>".getBytes());
            out.write("<a href='http://www.baidu.com'>hello</a>".getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                }
                if (socket != null) {
                    socket.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
// 客户端显示图片
public class PictureServer {
    public static void main(String[] args) throws Exception{
        //1.创建服务器套接对象
        ServerSocket server = new ServerSocket(8081);
        //2.通过accept()方法接收客户端连接
        Socket socket = server.accept();
        //3.获取输出流
        OutputStream out = socket.getOutputStream();
        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:image/jpeg\r\n".getBytes());// 读取文字和图片不同
        out.write("\r\n".getBytes());
        //4,通过字节流读取图片
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("second_stage\\network\\1.png"));
        int data;
        while ((data = bis.read()) != -1) {
            out.write(data);
        }
        //5.关闭资源
        server.close();
        socket.close();
        out.close();
        bis.close();
    }
}

客户端的实现:

Socket类:实现了客户端的套接字

public class Chat {
    public static void main(String[] args) throws IOException {
        //创建客户端套接字对象。并指定连接服务器端的ip和端口号
        Socket socket = new Socket("localhost",8081);
        socket.close();
    }
}

聊天案例

// 服务器
public static void main(String[] args) throws IOException {
        //1.创建服务端套接字对象
        ServerSocket server = new ServerSocket(8081);
        while (true) {
            //2.调用accept()等待客户端连接,将客户端封装成Socket对象
            Socket socket = server.accept();
            System.out.println("客户端连接成功");

            //二.服务端接收消息
            //3.通过Socket获取字节输入流,获取客户端发来的消息
            InputStream in = socket.getInputStream();
            byte[] bytes = new byte[2048];// 一个UTF-8中文字符占用3个字节
            //4.将输入流读取到的字节数据存储到字节数组中
            in.read(bytes);
            //5.将字节数组转换成字符串
            System.out.println(new String(bytes));

            //三.服务端发消息
            //6.通过Socket获取字节输出流,给客户端回消息
            OutputStream out = socket.getOutputStream();
            System.out.println("请输入消息:");
            String message = new Scanner(System.in).next();
            out.write(("服务端:"+message).getBytes());
        }
    }
// 客户端
public static void main(String[] args) throws IOException {
        while (true) {
            // 一.客户端发消息
            //1.创建客户端套接字对象,并指定要连接的服务端的ip和端口号
            Socket socket = new Socket("localhost",8081);
            //2.获取Socket对象提供的字节输出流,给服务端发送消息
            OutputStream out = socket.getOutputStream();
            //3.从控制台输入消息
            System.out.println("请输入消息:");
            String message = new Scanner(System.in).next();
            //4.通过输出流将消息写到服务端
            out.write(("客户端:"+message).getBytes());// 通过getBytes将字符转换成字节

            //四.客户端接受消息
            //5.从Socket对象中获取字节输入流,获取服务端发来的消息
            InputStream in = socket.getInputStream();
            byte[] bytes = new byte[2048];
            in.read(bytes);
            System.out.println(new String(bytes));
        }
    }

Java1.8的新特性

Lambda表达式

实现接口的方式:使用Lambda表达式(该接口只能有一个抽象方法)

函数式接口:只包含一个抽象方法的接口称为函数式接口,lambda表达式实现的接口都是函数式接口。可以用@FunctionalInterface注解来标注该接口为函数式接口

Lambda表达式的常见格式

    public static void main(String[] args) {
        // 箭头左边是接口中抽象方法要传递的参数
        // 箭头右边是返回的值或表达式
        
		//1.
        Calculate calculate = (int num1, int num2) -> num1 + num2;
        System.out.println(calculate.cal(1,2));

		//2.
        Calculate calculate1 = (a,b)->a+b;
        System.out.println(calculate1.cal(2,3));

		//3.
        Calculate calculate2 = (a, b) -> {
            System.out.print("结果是:");
            return a + b;
        };
        System.out.println(calculate2.cal(3,4));

		//4.
        int cal = ((Calculate) (x, y) -> x + y).cal(4, 5);
        System.out.println(cal);
    }

Stream

主要用于集合数据操作,对集合中数据进行统计等操作

1.将list转换成set

public static void main(String[] args) {
        //将list转换成set
        ArrayList<String> list = new ArrayList<>();
        list.add("富强");
        list.add("民主");
        list.add("文明");

        Set<String> collect = list.stream().collect(Collectors.toSet());
        System.out.println(collect);
    }

2.将list转换成map

public static void main(String[] args) {
        //将list转换成map
        ArrayList<People> people1 = new ArrayList<>();
        people1.add(new People("张三",18,'男'));
        people1.add(new People("李四",18,'男'));
        people1.add(new People("王五",18,'女'));
        //将People的name作为key,将People的年龄作为value
        //Collectors.toMap(Function fun1,Function fun2) 第一个参数是设置key值,第二个参数是设置value值

        //将People的name作为Key值,name是String类型,即泛型的第二个参数
        Function<People,String> function = peo->{
            return peo.getName();
        };
        Function<People,Integer> function1 = peo->{
            return peo.getAge();
        };
        Map<String, Integer> collect = people1.stream().collect(Collectors.toMap(function, function1));
        System.out.println(collect);
    }

设计模式

单例模式

在整个程序的运行过程中,保证实例化的对象只有一个,不能手动创建对象,对象由类方法提供,即自行实例化并向整个系统提供这个实例

饿汉式单例:对象在 类加载时 就创建好了

设计:

1.声明一个静态类型对象引用,并进行实例化

2.将构造方法用private修饰,避免在其它类中随意创建对象

3.编写一个返回对象的方法,用public修饰,供给调用者使用

public class Singleton {
    // 声明一个静态类型对象引用,并进行实例化
    private static Singleton singleton = new Singleton();

    // 将构造方法用private修饰
    private Singleton() {
    }

    // 编写一个返回对象的方法,用public修饰
    public static Singleton getInstance() {
        return singleton;
    }
}

懒汉式单例:对象在调用方法时才会被创建

设计:

1.不进行初始化

2.将构造方法用private修饰,避免在其它类中随意创建对象

3.编写一个返回对象的方法,用public修饰,判断当对象为空时才创建

public class LazySingleton {
    // 不进行初始化
    private static LazySingleton lazySingleton;
    
    // 将构造方法用private修饰
    private LazySingleton() {

    }
    
	// 编写一个返回对象的方法,用public修饰,判断当对象为空时才创建
    // 初次调用时为空,则会创建对象,待到第二次调用时,由于已经存在了实例对象,则会直接返回第一次创建的对象
    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

饿汉式和懒汉式区别:

1.饿汉式对象在类加载时被创建,如果该对象不使用,则会造成资源浪费

2.懒汉式线程不安全,可以用public synchronized static修饰方法来避免

工厂模式

工厂生成产品:

1.产品说明书(父类)

2.产品角色(通过说明书设计出来的产品)(子类)

3.工厂(根据客户需求来提供产品,产品就是对象)

简单工厂模式

产品说明书

public class Car {
    private String name;
    private String color;

    public Car() {
    }

    public Car(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

产品角色

public class BMW extends Car {
    public BMW() {
        super("华晨宝马","白色");
    }
}
public class Benci extends Car {
    public Benci() {
        super("奔驰","黑色");
    }
}

工厂

public class CarFactory {
    public Car createCar(String name) {
        switch (name) {
            case "BMW":
                return new BMW();
            case "Benci":
                return new Benci();
            default:
                System.out.println("没有该车");
                return null;
        }
    }
}

用户

public class Test {
    public static void main(String[] args) {
        //1.创建工厂
        CarFactory carFactory = new CarFactory();
        //2.获取BMW对象
        Car bmw = carFactory.createCar("BMW");
        System.out.println(bmw);
    }
}
工厂方法模式

产品说明书和产品角色同上

工厂

public interface CarFactory {
    // 造车的方法
    Car createCar();
}

具体工厂

public class BMWFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new BMW();
    }
}
public class BenciFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Benci();
    }
}

测试

public class Test {
    public static void main(String[] args) {
        CarFactory factory = new BMWFactory();
        Car car = factory.createCar();
        System.out.println(car);
    }
}

代理模式

静态代理

对象类

public class House {
    private int price = 1000;

    public House() {
    }

    public House(int price) {
        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

代理类

public class HouseProxy {
    public House getHouse() {
        // 代理对象需要拿到目标对象才能进行包装
        House house = new House();
        // 代理对对象进行一些拓展,修改和增强操作
        house.setPrice(house.getPrice()*2);
        // 再将对象返回,此时通过代理获取的对象就是被修改后的对象
        return house;
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        // 创建代理类
        HouseProxy proxy = new HouseProxy();
        // 通过代理获取被代理修改后的对象
        House house = proxy.getHouse();
        System.out.println(house.getPrice());
    }
}
动态代理

接口的实现类

public class UserServiceImpl implements UserService{
    @Override
    public void login(String password) {
        System.out.println(password);
        System.out.println("执行登录...");
    }

    @Override
    public void register() {
        System.out.println("执行注册...");
    }

    @Override
    public void logout() {
        System.out.println("执行退出...");
    }
}

测试类

    @Test
    public void test02() {
        // 使用Proxy创建一个代理类 反射
        // 第一个参数为:类加载器,用于加载代理类对象
        // 第二个参数为:被代理的类所实现的接口
        // 第三个参数为:代理执行器,指定被代理的对象以及执行的方式(新建类,并实现InvocationHandler接口)
        UserService service = (UserService) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(),
                UserServiceImpl.class.getInterfaces(),
                new ProxyHandler(new UserServiceImpl()));//创建代理类
        service.login("123456");// 在调用方法时,不是直接调用类中的方法,而是调用代理类中的方法
        service.register();
        service.logout();
    }

代理类

// 编写代理的业务逻辑代码,即代理所要做的事
public class ProxyHandler implements InvocationHandler {
    // 声明被代理对象
    Object object;

    // 初始化被代理对象
    public ProxyHandler(Object object) {
        this.object = object;
    }

    // 反射
    // proxy:指定方法是哪个对象的
    // method:封装了被代理类执行的方法
    // args:方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用方法,使用反射进行调用
        System.out.println(LocalDateTime.now());
        if (args != null) {
            String string = args[0].toString();// 在参数中获取password值
            string = "654321";// 修改
            args[0] = string;// 又赋给参数
        }
        Object obj = method.invoke(object, args);// 可以看做Object类携带参数args执行了method方法
        return obj;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值