0
点赞
收藏
分享

微信扫一扫

04.集合注解反射

雪域迷影 2022-04-29 阅读 68
java

集合:

ArrayList的特点:

1.List接口的实现类

2.通常可以根据下标进行操作

3.元素有下标,有序,可重复

4.底层结构是数组,内存空间是连续的

5.增删操作比较慢,查询操作比较快 ( 前提是数据量比较大时 )

6.初始容量为10,扩容以1.5倍扩容

ArrayList示例:

package cn.tedu.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**本类用于ArrayList的相关测试*/
public class TestArrayList {
    public static void main(String[] args) {
        //1.创建对应的集合对象
        /*底层会自动帮我们们创建数组来存放对象,并且数组的初始容量是10*/
        ArrayList<Integer> list = new ArrayList<>();
        //2.向集合中添加元素,用于测试
        list.add(100);
        list.add(200);
        list.add(300);
        list.add(400);
        list.add(300);
        list.add(400);
        System.out.println(list);
        //3.测试常用方法
//        list.clear();
//        System.out.println(list);//[]
        System.out.println(list.contains("100"));//false,"100"是String类型,是否包含指定元素"100"
        System.out.println();
        System.out.println(list.get(0));//100,取集合下标为0的元素
        System.out.println(list.indexOf(400));//3,判断元素第一次出现的下标
        System.out.println(list.lastIndexOf(400));//5.判断元素最后一次出现的下标

        /*下面的代码报错,数组下标越界,index:300  size:6
        * 主要是因为List中有两个重载的remove()方法,如果传入的是300
        * 会直接把这个300看作是int类型的index索引,所以如果想指定元素来删除数据
        * 需要把int类型的300手动装箱成Integer类型的300元素*/
        //System.out.println(list.remove(300));//报错,下标越界

        System.out.println(list);
        System.out.println(list.remove(Integer.valueOf(300)));//手动装箱,根据具体元素删除内容,参数为引用类型,int需要手动装箱
        System.out.println(list);
        System.out.println(list.remove(2));//根据下标删除元素
        System.out.println(list);
        System.out.println(list.set(2,777));//修改指定位置处的元素为777
        System.out.println(list);
        System.out.println(list.size());
        System.out.println(list);

        //测试集合间的操作
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        list2.add(4);
        System.out.println(list2);//将list2集合中的所有元素添加到list集合的末尾
        list.addAll(list2);//[100,200,777,400,1,2,3,4]
        System.out.println(list);
        list.addAll(0,list2);//将list2集合中的所有元素从list集合的0号位置开始添加
        System.out.println(list);//[1,2,3,4,100,200,777,400,1,2,3,4]

        list.removeAll(list2);//删除list集合中所有属于list2集合的元素
        System.out.println(list);//[100,200,777,400]
        list.add(1);
        System.out.println(list.containsAll(list2));//list集合中是否包含list2的所有元素

        list.retainAll(list2);//取交集,保留list中属于list2的所有元素
        System.out.println(list);//[1]

        ArrayList<Integer> list3 = new ArrayList<>();
        list3.add(1);
        list3.add(2);
        list3.add(3);
        list3.add(4);

        //测试集合的迭代
        /*1.通过for循环
         * 2.高效for循环
         * 3.iterator
         * 4.listIterator*/
        //方式1:因为List集合是有序的,元素是有下标的,所以可以根据下标进行遍历
        //从哪开始:0   到哪结束:list.size()-1    如何变化:++
        for (int i = 0; i < list3.size(); i++) {
            System.out.println(list3.get(i));
        }
        System.out.println("========");

        //方式2:因为普通for循环遍历效率低,语法复杂,所以使用高效for循环来遍历
        //格式:for(每轮遍历到的元素的类型  元素名:要遍历的元素){循环体}
        for(Integer n:list3){
            System.out.println(n);
        }
        System.out.println("========");

        //方式3:从父接口Collection中继承过来的迭代器
        //1.获取要迭代的集合对应的迭代器
        Iterator<Integer> it = list3.iterator();
        //2.通过迭代器判断是否有下一个元素可以迭代
        while (it.hasNext()){
            //3.通过迭代器获取本轮迭代的元素
            System.out.println(it.next());
        }

        System.out.println("=======");
        /*方式4:ListIterator属于List接口独有的迭代器
        Iterator<E>---父接口,常用方法hasNext()和next()
        * ListIterator<E>----子接口---可以使用父接口的功能,除了父接口的功能
        之外还有自己的特有功能,比如逆序遍历,添加元素等,但注意,不常用
        public interface ListIterator<E> extends Iterator<E>*/
        ListIterator<Integer> it2 = list3.listIterator();
        while(it2.hasNext()){
            System.out.println(it2.next());
        }
        System.out.println("==========");
        for (int i = list3.size()-1; i >=0 ; i--) {
            System.out.println(list3.get(i));
        }

    }
}

LinkedList:

请添加图片描述

LinkedList的特点:
1.List接口的实现类

2.元素有下标,有序,可重复

3.底层的数据结构是链表,内存空间不连续的

4.通过进行首尾节点的操作比较多

5.增删操作比较快,查询操作比较慢[数据量比较大时]

注意:LinkedList的查询操作也不是都慢,首尾操作还是比较快的

package cn.tedu.collection;
import java.util.LinkedList;
/**本类用于LinkedList的相关测试*/
public class TestLinkedList {
    public static void main(String[] args) {
        //1.创建集合对象
        LinkedList<String> li = new LinkedList<>();
        //2.添加数据
        li.add("孙悟空");
        li.add("猪八戒");
        li.add("唐三藏");
        li.add("沙师弟");
        li.add("白龙马");
        System.out.println(li);

        //测试LinkedList的独有方法
        li.addFirst("蜘蛛精");//添加首元素
        li.addLast("玉兔精");//添加尾元素
        System.out.println(li);//[蜘蛛精, 孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马, 玉兔精]

        System.out.println(li.getFirst());//获取首元素,蜘蛛精
        System.out.println(li.getLast());//获取尾元素,玉兔精
        System.out.println(li.removeFirst());//删除首元素
        System.out.println(li);//[孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马, 玉兔精]
        System.out.println(li.removeLast());//删除尾元素
        System.out.println(li);//[孙悟空, 猪八戒, 唐三藏, 沙师弟, 白龙马]

        /*别名:查询系列*/
        System.out.println(li.element());//孙悟空,获取首元素
        System.out.println(li.peek());//孙悟空,获取首元素
        System.out.println(li.peekFirst());//孙悟空,获取首元素
        System.out.println(li.peekLast());//白龙马,获取尾元素

        /*别名:新增系列*/
        System.out.println(li.offer("海绵宝宝"));//添加尾元素
        System.out.println(li);
        System.out.println(li.offerFirst("派大星"));//添加首元素
        System.out.println(li);
        System.out.println(li.offerLast("蟹老板"));//添加尾元素
        System.out.println(li);

        /*别名:移除系列*/
        System.out.println(li.poll());//删除首元素
        System.out.println(li.pollFirst());//删除首元素
        System.out.println(li.pollLast());//删除尾元素
        System.out.println(li);

    }
}

Map集合:

  1. Map可以根据键来提取对应的值
  2. Map的键不允许重复,如果重复,对应的值会被覆盖
  3. Map存放的都是无序的数据
  4. Map的初始容量是16,默认的加载因子是0.75

常用方法:

void clear() 从此映射中移除所有映射关系(可选操作) boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图
boolean equals(Object o) 比较指定的对象与此映射是否相等
V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() 返回此映射的哈希码值
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true
Set keySet() 返回此映射中包含的键的 Set 视图
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size() 返回此映射中的键-值映射关系数
Collection values() 返回此映射中包含的值的 Collection 视图

在这里插入图片描述

Map的常用方法与迭代:

package cn.tedu.map;
import java.util.*;
/**本类用于测试Map接口*/
public class MapDemo {
    public static void main(String[] args) {
        //1.创建集合Map对象
        /*Map中的数据要符合映射规则,一定注意要同时指定K和V的数据类型
        * 至于这个K和V具体要设置成什么类型,取决于具体的业务需求*/
        Map<Integer,String> map = new HashMap<>();
        //2.向map集合存入数据,注意方法是put()方法,并且需要存入一对的<K,V>的值
        map.put(9527,"白骨精");
        map.put(9528,"黑熊精");
        map.put(9529,"锂鱼精");
        map.put(9530,"黄毛怪");
        map.put(9527,"女儿国国王");
        map.put(9531,"黑熊精");
        /*1.Map中的K不允许重复,如果重复,后面的value会把前面的value覆盖掉
        * 比如:女儿国国王和白骨精,的key都是9527,白骨精就被覆盖了
        * 2.Map中的value可以重复,比如我们有两个黑熊精*/
        System.out.println(map);

        //3.进行方法测试
        //map.clear();//清空数据
        //System.out.println(map);//{}
        System.out.println(map.isEmpty());//false,判断集合是否为空
        System.out.println(map.size());//获取集合中键值对的对数
        System.out.println(map.containsKey(9527));//true,判断集合是否包含9527的key
        System.out.println(map.containsValue("白骨精"));//false,判断集合是否包含指定的值value
        System.out.println(map);
        System.out.println(map.remove(9529));//根据key删除对应的这一对键值对
        Collection<String> c = map.values();//将Map中所有的value值取出,存入Collection中
        System.out.println(c);

        //4.map集合的迭代方式1
        /*Map的迭代方式1:
        * 遍历map中的数据,但是map本身没有迭代器,所以需要先转换成set集合
        * 用到的方法:keySet():把map中的所有key值存入到set集合中,返回值Set<Key>
        */
        //4.1将map集合的所有key值取出,存入Set集合中,Set集合的泛型就是key的类型
        Set<Integer> set = map.keySet();
        //4.2迭代Set集合,首先获取迭代器
        Iterator<Integer> it = set.iterator();
        //4.3循环,判断是否有下一个元素可以迭代
        while(it.hasNext()){
            //4.4获取本轮循环到的元素,就是set中存的每个key
            Integer key = it.next();
            //4.5根据key获取map中的value值
            String value = map.get(key);
            System.out.println("{"+key+","+value+"}");//拼接打印
        }
        /*Map的迭代方式2:
        * 遍历Map集合,需要把map集合先转成set集合
        * 是把map中的一对健值对key&value作为一个Entry<K,V>整体放入set
        * 一对K,V就是一个Entry*/
        //将Map集合的Entry转成Set集合
        Set<Map.Entry<Integer,String>> set2 = map.entrySet();
        //获取Set集合的迭代器
        Iterator<Map.Entry<Integer, String>> it2 = set2.iterator();
        //循环,判断是否有下一个元素可以迭代
        while (it2.hasNext()){
            Map.Entry<Integer, String> entry = it2.next();//获取Set集合的元素,每个元素都是一个Entry健值对
            System.out.println(entry);//打印Set集合元素Entry值
            Integer key = entry.getKey();//获取Entry的健
            String value = entry.getValue();//获取Entry的值
            System.out.println("{"+key+","+value+"}");//拼接打印
        }
    }
}

Set集合:

  1. Set是一个不包含重复数据的Collection
  2. Set集合中的数据是无序的(因为Set集合没有下标)
  3. Set集合中的元素不可以重复 – 常用来给数据去重

集合Set的常用方法和迭代器:

package cn.tedu.collection;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**本类用于测试Set*/
public class TestSet {
    public static void main(String[] args) {
        //1.创建对应的集合对象
        Set<String> set = new HashSet<>();
        set.add("紫霞仙子");
        set.add("至尊宝");
        set.add("蜘蛛精");
        set.add("紫霞仙子");
        /*1.Set集合中的元素都是没有顺序的
        * 2.Set集合中的元素不能重复
        * */
        System.out.println(set);

        //3.常用方法
        System.out.println(set.contains("唐僧"));//false,判断是否包含指定元素
        System.out.println(set.isEmpty());//false,判断集合是否为空
        System.out.println(set.remove(null));//true,移除集合中的元素null
        System.out.println(set);//[蜘蛛精, 至尊宝, 紫霞仙子]
        System.out.println(set.size());//3,获取集合中的元素个数
        //把Set集合转为数组,然后用Arrays.toString()查看数组元素
        System.out.println(Arrays.toString(set.toArray()));//[蜘蛛精, 至尊宝, 紫霞仙子]


        //4.测试集合间的
        Set<String> set2 = new HashSet<>();
        set2.add("小兔纸");
        set2.add("小脑斧");
        set2.add("小海藤");
        set2.add("小狮几");
        System.out.println(set2);
        //将集合set中的所有元素添加到set2集合中
        set2.addAll(set);
        System.out.println(set2);
        //判断set集合中是否包含set2的所有元素
        System.out.println(set.containsAll(set2));//false
        //移除集合set2中属于集合set中的所有元素
        System.out.println(set2.removeAll(set));
        System.out.println(set2);
        //取set2集合中属于set集合的公共元素
        System.out.println(set2.retainAll(set));
        System.out.println(set2);//[]


        //5.集合的迭代器
        //获取迭代器
        Iterator<String> it = set.iterator();
        //循环迭代,判断是否有下一个元素可以迭代
        while(it.hasNext()){
            //打印循环迭代到的元素
            System.out.println(it.next());
        }
    }
}

测试Set集合存对象:

package cn.tedu.collection;
import java.util.HashSet;
import java.util.Set;

/**本类用于测试Set存放自定义引用类型对象的去重问题*/
public class TestSet2 {
    public static void main(String[] args) {
        //1.创建Set集合对象
        Set<Student> set = new HashSet<>();
        //2.创建Student类对象
        Student s1 = new Student("张三",3);
        Student s2 = new Student("李四",4);
        Student s3 = new Student("王五",5);
        Student s4 = new Student("王五",5);
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        /*如果Set集合中存放的是我们自定义的类型
        那么需要给自定义类中添加重写的equals()与hashCode()方法,
        才有会实现去重的效果
        不然,Set集合认为s3与s4的地址值不同,是两个不同的对象,不会去重*/
        System.out.println(set);
        //需求:定义小狗类Dog,将小狗类的对象存入Set集合
        //实现以类型 属性 属性值去重的效果

        Set<Dog> dogset = new HashSet<>();
        Dog d1 = new Dog("伍","旺",3);
        Dog d2 = new Dog("tg","旺旺",4);
        Dog d3 = new Dog("rrr","旺旺旺",5);
        Dog d4 = new Dog("rrr","旺旺旺",5);
        dogset.add(d1);
        dogset.add(d2);
        dogset.add(d3);
        dogset.add(d4);
        System.out.println(dogset);
    }
}

class Student {
    //2.创建属性
    String name;//姓名
    int id;//学号
    //3.提供全参构造

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }
    //4.添加重写的toString()方法

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

    //5.添加重写的equals()和hashCode()方法,比较对象的类型+属性+属性值
    @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 &&
                Objects.equals(name, student.name);
    }

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

class Dog {
    String host;
    String name;
    int age;

    public Dog() {}

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Dog dog = (Dog) o;
        return age == dog.age &&
                Objects.equals(host, dog.host) &&
                Objects.equals(name, dog.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(host, name, age);
    }
}

Collection:集合层次的根接口

List:有序,有下标,可以重复

ArrayList:底层结构是数组,内存空间连续,常用于下标操作

LinkedList:底层结构是链表,

Set:无序,无下标,不可以重复

Map:<K,V> key&value 键值对 映射关系 Entry

​ Map.Entry<K,V>:内部类的应用

​ map中的key不允许重复.

package cn.tedu.review;
import java.util.*;
/*本类用于练习Map案例,统计字符串中字符的个数
* 需求效果:用户输入aabbcc,输出{a=2,b=2,c=2}*/
public class TestMap {
    public static void main(String[] args) {
        //1.提示并接收用户输入的字符串
        System.out.println("请您输入要统计的字符串:");
        String input = new Scanner(System.in).nextLine();
        //2.准备一个map集合,用来存放出现的字符与字符的次数
        //也就是说字符不允许重复,但字符出现的次数可以重复
        //也就是说字符不允许重复,但字符出现的次数可以重复
        Map<Character,Integer> map = new HashMap<>();

        //3.准备向map中存入数据
        //3.1遍历用户输入的字符串,拿到每个字符
        for (int i = 0; i < input.length(); i++) {
            char key = input.charAt(i);
            //System.out.println(key);//打印查看每轮循环获取到的字符,没有问题
            //3.2根据刚刚获取到的key拿到对应的value
            Integer value = map.get(key);
            if(value == null){//之前这个字符没有出现过,次数还是Integer的默认值null
                map.put(key,1);//没出现过,次数就设置为1
            }else{//value不是null,说明之前已经存过次数了
                map.put(key,++value);//出现过,次数就设置为之前的次数+1
            }
        }
        System.out.println("各个字符出现的次数为:"+map);
    }
}

进程,线程:

进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。

进程的特点:

独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.

线程的概念:

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。

进程与线程的关系:

进程与线程的关系

每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间.
所以想使用线程技术,得先有进程,进程的创建是OS操作系统来创建的,一般都是C或者C++完成

进程与线程的关系

什么是进程?什么是程序?有什么区别?

程序:数据与指令的集合,程序是静态的

进程:给程序加入了时间的概念,是动态的,代表是操作系统OS中正在运行的程序,具有独立性,动态性,并发性

什么是线程?线程和进程有什么关系?

线程是操作系统OS能够进行运算调度的最小单位

一个进程可以有多个线程,也可以只有一个线程

只有一个线程的进程称作单线程程序

CPU:电脑的核心处理器,类似于人的大脑

多线程的特性:

我们宏观上觉得多个进程是同时运行的,但实际的微观层面上,一个CPU【单核】只能执行一个进程中的一个线程。
那为什么看起来像是多个进程同时执行呢?
是因为CPU以纳秒级别甚至是更快的速度高效切换着,超过了人的反应速度,这使得各个进程从看起来是同时进行的,也就是说,宏观层面上,所有的进程看似并行【同时运行】,但是微观层面上是串行的【同一时刻,一个CPU只能处理一件事】。

串行是指同一时刻一个CPU只能处理一件事,类似于单车道
并行是指同一时刻多个CPU可以处理多件事,类似于多车道

时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:

FCFS(First Come First Service 先来先服务算法)
SJS(Short Job Service短服务算法)

线程的三态:

由于线程状态比较复杂,我们由易到难,先学习线程的三种基础状态及其转换,简称”三态模型” :

就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
线程的3种状态

就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行

我们可以再添加两种状态:

  • 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
  • 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

线程的5种状态

线程生命周期,主要有五种状态:

新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

线程状态与代码对照

多线程编程实现方案1 ( 创建类继承多线程类Thread ) :

以多线程的方式运行程序:

package cn.tedu.thread;
/*本类用于多线程编程实现方案1:继承Thread类的方式来完成*/
public class TestThread1 {
    public static void main(String[] args) {
        //4.创建线程对象进行测试
        /*4.new对应的是线程的新建状态*/
        /*5.要想模拟多线程,至少得启动2个线程
        * 如果只启动1个线程,是单线程程序*/
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        /*6.这个run()方法如果直接这样调用,是没有多线程抢占执行的效果的
         * 这个写法只是把代码看作是普通方法来调用,谁先调用,谁先执行*/
        /*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中
        * 至于这个线程什么时候执行,这就是多线程执行的随机性,需要等待操作系统OS第二名并分配CPU
        * 8.线程的执行具有随机性,也就是说t1-t4具体怎么执行
        * 取决于Cpu的调度与时间片的分配,我们是决定不了的
        * 9.执行start()方法时,底层会自动调用我们重写的,包含自己业务的run()方法*/
//        t1.run();
//        t2.run();
//        t3.run();
//        t4.run();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义一个多线程类
class MyThread extends Thread{
    /*1.多线程编程实现方案1:继承Thread类+重写run()*/
    @Override
    public void run() {
        /*2.super.run()表示的是调用父类的业务,我们现在想用自己的业务
        * 所以注释掉*/
        //super.run();
        //3.完成自己的业务:打印10次当前正在执行的线程名称
        for (int i = 0; i < 10; i++) {
            /*3.getName()表示可以获取当前正在执行的线程名称
            * 由于本类继承了Thread类,所以可以直接使用Thread类的这个方法*/
            System.out.println(i+"="+getName());
        }
    }
}

多线程编程实现方案2 ( 创建类实现多线程接口Runnable ) :

以多线程的方式运行程序:

package cn.tedu.thread;
/**本类用于多线程编程实现方案2:实现Runnable接口来完成*/
public class TestThread2 {
    public static void main(String[] args) {
        //4.创建自定义对象---目标业务类对象
        MyRunnable target = new MyRunnable();
        //5.创建多个线程对象
        /*如何以多线程的方式启动线程?需要调用start()方法
        * 自己没有,需要与Thread类建立联系:Thread(Runnable target)
        * 但是由于Runnable是接口,无法实例化,我们可以把接口实现类对象
        * 也就是我们的自定义类的对象作为构造方法的参数传入,与Thread类建立关系*/
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}


//1.自定义多线程类
class MyRunnable implements Runnable{
    //2.添加接口中未实现的抽象方法,用于完成业务
    @Override
    public void run() {
        //3.写业务:打印10次当前正在执行的线程名称
        for (int i = 0; i <1000; i++) {
            /*问题:自定义类与父接口Runnable中都没有获取线程名字的方法
            * 所以还需要从Thread类中找:
            * currentThread():静态方法,由刚刚获取到的线程对象调用,返回当前线程对象的名字*/
            System.out.println(i+"="+Thread.currentThread().getName());
        }

    }
}

多线程的实现方案总结:

方案1:直接继承多线程父类 extends Thread

​ 1.定义自己的多线程类,并且继承Thread

​ 2.重写父类的run()方法,里面在是我们自己的业务

​ 3.创建自定义线程类对象

​ 4.通过线程对象调用start()方法,将线程加入到就绪队列中

​ 5.运行程序查看多线程抢占资源的效果

方案2:实现多线程接口 implements Runnable

​ 1. 定义自己的业务类,并实现接口Runnable

​ 2.在业务类中添加接口里的抽象方法run(),并实现业务

​ 3.创建唯一的业务类对象

​ 4.创建多个Thread类的对象,作为多个线程对象,并将刚刚的业务对象作为参数,传入Thread类的构造方法中Thread(Runnable target)

​ 5.使用多个线程类对象调用start()方法,将线程加入到就绪队列之中

​ 6.查看多线程抢占资源的效果

方案2的优点:
1.没有继承,耦合性不强,后续扔然可以继承或者实现其它的接口,比较自由

2.可以给所有的线程对象统一业务,业务只需要发布一次,保持统一

3.面向接口进行编程,代码更加高级

设计多线程模型,4个售票窗口共计售票100张:

方式一:继承Thread

package cn.tedu.tickets;
/**需求:设计多线程模型,4个售票窗口共计售票100张
 * 本方案使用多线程编程实现方案1:继承Thread类的方式来完全*/
public class TestThread {
    public static void main(String[] args) {
        TicketThread t1 = new TicketThread("窗口1");
        TicketThread t2 = new TicketThread("窗口2");
        TicketThread t3 = new TicketThread("窗口3");
        TicketThread t4 = new TicketThread("窗口4");
        //4.以多线程的方式启动线程对象
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义多线程售票类,继承Thread
class TicketThread extends Thread{
    //3.定义成员变量,用来保存票数,全类共享
    /*问题:4.线程对象共计售票400张,原因是创建了4个本类的对象,各自操作各自的成员变量
    * 解决:让所有对象共享同一个变量,这个变量需要设置为静态的*/
    //int tickets = 100;
    static int tickets = 100;
    /*添加本类的含参构造,用于给线程对象起名字
    * 由于只添加了这一个含参构造,默认的无参构造被覆盖掉了*/
    public TicketThread(String name) {
        /*表示调用父类的含参构造,因为实际上起名的功能是父类完成的
        * 所以我们需要将本构造方法接到的名字参数,传给父类的构造方法,让父类起名字*/
        super(name);
    }
    //2.重写run()方法,里面是我们的业务
    @Override
    public void run() {
        //4.1循环卖票
        while (true){
            //打印当前正在卖票的线程名称,并且票数-1
            System.out.println(getName()+"="+tickets--);
            //4.3做判断,如果没有票了,就退出死循环
            if(tickets<=0) break;//注意!死循环必须设置出口!
            //如果if的{}里的语句只有一句,大括号可以省略不写
        }
    }
}

方式二:实现接口Runnable

package cn.tedu.tickets;
/**需求:设计多线程模型,4个窗口共计售票100张
 * 本方案使用多线程编程方案2,实现Runnable接口的方式来完成*/
public class TestRunnable {
    public static void main(String[] args) {
        //5.创建自定义类的对象,并将唯一的业务对象作为参数传入
        TicketsRunnable target = new TicketsRunnable();
        //6.创建多个线程对象
        Thread t1 = new Thread(target,"窗口1");
        Thread t2 = new Thread(target,"窗口2");
        Thread t3 = new Thread(target,"窗口3");
        Thread t4 = new Thread(target,"窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
//1.定义一个TicketRunnable类实现Runnable接口
class TicketsRunnable implements Runnable{
    //3.定义变量,保存票数
    /*由于自定义的类对象,作为唯一的业务对象,只new了一次
    * 所以发布的任务,也就是票数天然的会被所有线程对象共享,无需设置为静态也只有一份*/
    int tickets = 100;
    //2.添加父接口未实现的方法,里面写我们自己的业务
    @Override
    public void run() {
        //4.循环卖票
        while (true){
            System.out.println(Thread.currentThread().getName()+"="+tickets--);
            if(tickets<=0) break;
        }
    }
}

同步锁:

我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:

​ 在多线程程序中 + 有共享数据 + 多条语句操作共享数据

同步与异步:

同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
坏处就是效率会降低,不过保证了安全。
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
坏处就是有安全隐患,效率要高一些。

synchronized同步关键字:

synchronized (锁对象) {undefined
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}

同步效果的使用有两个前提:

前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)

前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

同步线程锁 ( 锁必须得唯一 )

双重校验机制,解决卖票机超卖重卖 问题:

package cn.tedu.review;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
    public static void main(String[] args) {
        //4.创建自定义类对象,作为唯一的业务对象
        TicketRunnable target = new TicketRunnable();
        //5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
        Thread t1 = new Thread(target, "窗口1:");
        Thread t2 = new Thread(target, "窗口2:");
        Thread t3 = new Thread(target, "窗口3:");
        Thread t4 = new Thread(target, "窗口4:");
        //6.以多线程方式启动程序
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
 * 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
 * 锁的范围:不能太大,太大了,干什么都要排队,效率低;
 *          不能太小,太小了,锁不住,还是会出现安全隐患
 * 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
    //3.定义变量,票数100
    int tickets = 100;
    Object o = new Object();
    Dog d =new Dog();
    //2.添加接口中未实现的方法,里面是我们自己的业务
    @Override
    public void run() {
        //3.循环卖票
        while (true) {
            /*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
            /*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/

            /*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
            * 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
            //synchronized (new Object()) {
            /*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
            synchronized (d) {//同步代码块解决的是重卖的问题
                //7.让线程休眠,增加出错概率
                if(tickets>0){//如果有票,就卖票(双重校验)
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + tickets--);
                }
                if (tickets <= 0) break;
            }
        }
    }
}
class Dog{}

锁是唯一的,类的字节码文件也是唯一的,直接用类的字节码文件名作为同步锁 ( 改善 )

双重校验机制,解决卖票机超卖重卖 问题:

package cn.tedu.review;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
    public static void main(String[] args) {
        //4.创建自定义类对象,作为唯一的业务对象
        TicketRunnable target = new TicketRunnable();
        //5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
        Thread t1 = new Thread(target, "窗口1:");
        Thread t2 = new Thread(target, "窗口2:");
        Thread t3 = new Thread(target, "窗口3:");
        Thread t4 = new Thread(target, "窗口4:");
        //6.以多线程方式启动程序
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
 * 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
 * 锁的范围:不能太大,太大了,干什么都要排队,效率低;
 *          不能太小,太小了,锁不住,还是会出现安全隐患
 * 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
    //3.定义变量,票数100
    int tickets = 100;
    //Object o = new Object();
    //Dog d =new Dog();
    //2.添加接口中未实现的方法,里面是我们自己的业务
    @Override
    public void run() {
        //3.循环卖票
        while (true) {
            /*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
            /*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/

            /*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
            * 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
            //synchronized (new Object()) {
            /*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
            synchronized (TicketRunnable.class) {//同步代码块解决的是重卖的问题
                //7.让线程休眠,增加出错概率
                if(tickets>0){//如果有票,就卖票(双重校验)
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + tickets--);
                }
                if (tickets <= 0) break;
            }
        }
    }
}
//class Dog{}

创建线程池的工具类对象Executors

常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象

创建出来的线程池对象是ExecutorService:这个池子用来新建/启动/存储/销毁线程

package cn.tedu.review;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**本类用于复写售票案例实现方案2*/
public class TestRunnable {
    public static void main(String[] args) {
        //4.创建自定义类对象,作为唯一的业务对象
        TicketRunnable target = new TicketRunnable();
        //5.创建多个线程对象,并且把唯一的对象交给线程对象来干活
//        Thread t1 = new Thread(target, "窗口1:");
//        Thread t2 = new Thread(target, "窗口2:");
//        Thread t3 = new Thread(target, "窗口3:");
//        Thread t4 = new Thread(target, "窗口4:");
        //6.以多线程方式启动程序
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        /*Executors是用来辅助创建线程池的工具类对象
        * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
        * 创建出来的线程池对象是ExecutorService:这个池子用来新建/启动/存储/销毁线程*/
        ExecutorService pool = Executors.newFixedThreadPool(5);//参数5是指线程池最多不超过5个线程
        for (int i = 0; i < 5; i++) {
            /*execute()方法是让线程池中的线程来执行业务,每次调用这个方法
            * 都会将一个线程加入到就绪队列中,但注意,具体执行还得看操作系统OS是否选中*/
            
           //本方法的参数是Runnable接口对象,就是我们实现类写的业务,也就是业务类对象target
            pool.execute(target);
        }
    }
}
/*多线程出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*解决方案 同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全的代码
 * 加锁之后,就有了同步(排队)的效果,加锁需要考虑:
 * 锁的范围:不能太大,太大了,干什么都要排队,效率低;
 *          不能太小,太小了,锁不住,还是会出现安全隐患
 * 并且注意!!!多个线程间,必须使用同一把锁,否则锁不住!!!*/
//2.自定义多线程售票类
class TicketRunnable implements Runnable {
    //3.定义变量,票数100
    int tickets = 100;
    //Object o = new Object();
    //Dog d =new Dog();
    //2.添加接口中未实现的方法,里面是我们自己的业务
    @Override
    public void run() {
        //3.循环卖票
        while (true) {
            /*同步代码块:synchronized(唯一的锁对象){会出现安全隐患的所有代码块}*/
            /*同步代码块在同一时刻,同一资源只会被一个线程对象独享*/

            /*这种写法不对,相当于每个线程进来时,执行这句话都会new一个新的锁对象
            * 多个线程间使用的不是同一个唯一的锁对象,所以锁不住*/
            //synchronized (new Object()) {
            /*修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一*/
            synchronized (TicketRunnable.class) {//同步代码块解决的是重卖的问题
                //7.让线程休眠,增加出错概率
                if(tickets>0){//如果有票,就卖票(双重校验)
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + tickets--);
                }
                if (tickets <= 0) break;
            }
        }
    }
}
//class Dog{}

注解:

package cn.tedu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**类用于完成自定义注解*/
public class TestAnnotation {

}
//2.通过@Target注解标记自定义注解可以使用的位置
/*3.通过元注解@Target规定自定义注解可以使用的位置
 * 我们使用"ElementType.静态常量"的方式来指定自定义注解可以加在什么位置
 * 而且,值可以写多个,格式@Target({ElementType.XXX1,ElementType.XXX2})*/
@Target({ElementType.METHOD, ElementType.TYPE})
//3.通过@Retention注解标记自定义注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
/*4.通过元注解@Retention规定自定义注解的生命周期
* 我们使用"RetentionPolicy.静态常量"的方式来指定自定义注解的生命周期
* 注意:值只能写一个:SOURCE  CLASS  RUNTIME  3选1*/
//1.定义自定义注解
        /*1.首先注意:注解定义的语法与java不同
         * 2.定义自定义注解的格式:@interface 注解名*/
@interface Rice{
    //5.我们可以给注解进行功能增强--添加注解的属性
    /*6.注意:int eat();不是方法的定义,而是给自定义注解添加了一个age属性*/
    //int age();//给自定义注解添加了一个普通属性age,类型为int
    int age() default 0;//给自定义注解的普通属性赋予默认值0
    /*7.注解中还可以添加特殊属性value
    * 特殊属性的定义方式与普通属性一样,主要是使用方式不同
    * 注意:特殊属性的名字必须叫value,但是类型不做限制
    * 特殊属性也是可以赋予默认值的,格式与普通属性的赋值一样,不能简写*/
    //String value();//定义一个特殊属性value,类型是String
    String value() default "Lemon";//定义特殊属性,并且给特殊属性赋予默认值
}
/*5.注解使用的格式:@注解名*/
//4.定义一个类,用来测试自定义注解
//@Rice
class TestAnno{
    /*测试1:分别给TestAnno类  name属性  eat()方法上都添加Rice注解
    * 结论:属性上的注解报错,说明自定义注解可以加在什么位置,由@Target决定*/
    //@Rice
    String name;
    /*测试2:当我们给Rice注解添加了一个age属性以后,@Rice注解使用时直接报错
    * 结论:当注解没有定义属性时,可以直接使用
    *       当注解定义了属性以后,必须给属性赋值,格式:@Rice(age = 10)*/
    //@Rice(age = 10)
    /*测试3:给age属性赋予默认值以后,可以直接使用@Rice注解
    * 不需要给age属性赋值,因为age属性已经有默认值0了*/
    
    /*测试4:给Rice注解添加了特殊属性value以后,也必须给属性赋值
    * 只不过特殊属性赋值时可以简写成:@Rice("Apple")*/
    
    /*测试5:如果特殊属性也赋予了默认值,那么可以直接使用这个注解
    * 如果想要给注解的所有属性赋值,每条赋值都不能简写*/
    //@Rice("Apple")
    //@Rice
    @Rice(age = 10,value = "orange")
    public void eat(){
        System.out.println("eat方法");
    }
}
package cn.tedu.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class DemoTest {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@interface Cat{
    String name() default "";
    int value() default 0;
}

@Cat(name="tom")
class Test{
    @Cat(666)
    String host;
    @Cat(name="jack",value=999)
    public void eat(){
        
    }
}

设计模式:

23种设计模式

单例模式:可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个。
简单来说,保证一个类在内存中的对象就一个。

底层的实现思路一共分为了3个步骤:

对本类构造方法私有化,防止外部调用构造方法创建对象

创建全局唯一的对象,也做私有化处理

通过自定义的公共方法将创建好的对象返回(类似封装属性后的getXxx() )

单例设计模式方案1:饿汉式

package cn.tedu.design;
/**本类用于实现单例设计模式方案1*/
public class Singleton1 {
    public static void main(String[] args) {
        //new Mysingle();//构造方法私有化后,外面不可以用
        MySingle s1 = MySingle.getSingle();
        MySingle s2 = MySingle.getSingle();
        System.out.println(s1==s2);//true,只new了一次对象,静态资源全类共享
    }
}

//1.创建自己的单例程序
class MySingle{
    //2.提供本类的构造方法,并将构造方法私有化
    /*1.构造方法私有化的目的:为了防止外界随意调用,创建本类对象*/
    private MySingle(){}
    //3.创建本类的对象
    //4.2由于静态资源只能调用静态资源,所以single对象也需要设置成静态
    static private MySingle single = new MySingle();
    //4.提供公共的访问方式,返回刚刚创建好的对象
    //4.1为了不通过对象,直接调用本方法,需要将本方法设置为静态
    static public MySingle getSingle(){
        /*后续可以添加权限校验*/
        return single;
    }
}

Day17复习:

注解的快速回顾

package cn.tedu.review;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**本类用于注解的快速回顾*/
public class TestAnnotation {
}

//3.自定义注解一直到字节码文件中都生效
@Retention(RetentionPolicy.CLASS)
//2.自定义注解可以作用于属性与类
@Target({ElementType.TYPE,ElementType.FIELD})
//1.定义自定义注解Rice
@interface Rice{
    //4.自定义注解拥有普通属性char gender
    char gender() default '女';
    //5.自定义注解拥有特殊属性String name
    String value() default "枪哥yyds";
}

//6.准备测试类
@Rice(gender = '男',value = "枪哥yyds")
class TestAnno{
    //@Rice
    //@Rice(gender = '男')
    @Rice(value = "詹姆斯·高斯林yyds")
    String kind;
    public void eat(){
        System.out.println("eat()");
    }
}

单例模式方案1饿汉式回顾:

package cn.tedu.review;
/**本类用于复写单例设计模式方案1*/
public class Singleon1 {
    public static void main(String[] args) {
        Mysingle s1 = Mysingle.getSingle();
        Mysingle s2 = Mysingle.getSingle();
        System.out.println(s1==s2);
        System.out.println(s1);
        System.out.println(s2);
    }
}

//1.创建自己的单例模式
class Mysingle{
    //2.提供构造方法,并封装私有化
    private Mysingle(){}
    //3.创建本类的对象,将对象的地址值交给引用类型的变量来保存
    private static Mysingle single = new Mysingle();
    //4.提供一个公共的访问方式,向外界提供创建好的唯一的本类对象
    public static Mysingle getSingle(){
        return single;
    }
}

单例模式方案2:懒汉式

package cn.tedu.design;
/**本类用于实现单例设计模式方案2:懒汉式*/
/*关于单例模式的两种实现方式:
* 1.饿汉式:不管你用不用这个类的对象,都会直接先创建一个
* 2.懒汉式:先不给你创建这个类的对象,等你需要的时候再创建---延迟加载的思想
* 延迟加载的思想:是指不会在第一时间就把对象创建好占用内存,
*               而是什么时候用到,什么时候再去创建对象
* 3.线程安全问题:由于我们存在唯一的对象single2,并且多条语句都操作了这个变量
* 所以,如果将程序放在多线程的环境下,就容易出现数据安全问题
* 解决方案:
* 1)将3条语句都使用同步代码块包裹,保证同步排队的效果
* 2)由于getSingle()方法里也只有这3条语句,所以也可以将本方法修饰为同步方法
* 注意:被synchronized修饰的方法称作同步方法,但是不推荐使用!!!*/
public class Singleton2 {
    public static void main(String[] args) {
        Mysingle2 s1 = Mysingle2.getSingle();
        Mysingle2 s2 = Mysingle2.getSingle();
        System.out.println(s1==s2);
        System.out.println(s1);
        System.out.println(s2);
    }
}

//1.创建自己的单例程序MySingle2
class Mysingle2{
    //2.私有化本类的构造方法
    private Mysingle2(){}
    //3.创建的是本类对象对应类型的引用类型变量,用来保存对象的地址值
    //singele2是本类的成员变量,引用类型的默认值是null
    private static Mysingle2 single2;
    //4.提供公共的静态方法,将创建好的对象返回
    //synchronized public static Mysingle2 getSingle(){
    public static Mysingle2 getSingle(){
        synchronized (Mysingle2.class) {
            if (single2 == null) {
                single2 = new Mysingle2();
            }
            return single2;
        }
    }
}

反射:

Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

创建字节码对象的三种方式:

方式一:Class.forName(“类的全路径”);

方式二:类名.class

方式三:对象名.getClass()

获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
/**本类用于测试反射*/
public class TestReflect {
    /*单元测试方法的格式:public + void + 无参 + @Test*/
    //1.通过单元测试方法,获取目标类Student对应的字节码对象
    @Test
    public void getClazz() throws ClassNotFoundException {
        //练习获取字节码对象的3种方式
        Class<?> clazz1 = Class.forName("cn.tedu.reflection.Student");//方式一
        Class<?> clazz2 = Student.class;//方式二
        Class<?> clazz3 = new Student().getClass();//方式三

        //打印获取到的字节码对象
        System.out.println(clazz1);//class cn.tedu.reflection.Student
        System.out.println(clazz2);//class cn.tedu.reflection.Student
        System.out.println(clazz3);//class cn.tedu.reflection.Student
        //通过刚刚获取到的字节码对象,获取Student类的资源

        //获取字节码对象对应的全路径名
        System.out.println(clazz1.getName());//cn.tedu.reflection.Student
        //获取字节码对象对应的类名
        System.out.println(clazz1.getSimpleName());//Student
        //获取Student类对应的包对象
        System.out.println(clazz1.getPackage());//package cn.tedu.reflection
        //先获取Student类对应的包对象,再通过这个包对象,获取包名
        System.out.println(clazz1.getPackage().getName());//cn.tedu.reflection
    }

    //2.通过单元测试方法,获取Student类中的成员变量
    @Test
    public void getFie() throws ClassNotFoundException {
        //获取目标类对应的字节码对象
        Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
        //通过字节码对象获取成员变量们
        Field[] fs = clazz.getFields();
        //打印数组,查看数组中的内容
        System.out.println(Arrays.toString(fs));
        //遍历数组
        for(Field f:fs){
            /*注意:目前成员变量的修饰符需要是public才能获取到,其它修饰符可以通过暴力反射获取*/
            System.out.println(f);//打印本轮循环到的数组中的字段对象
            System.out.println(f.getName());//通过字段对象获取本字段的名字
            System.out.println(f.getType());//通过字段对象获取本字段的类型
        }
    }

    //3.通过单元测试方法,获取Student类中的成员方法
    @Test
    public void getFuction(){
        //1.获取目标类对应的字节码对象
        Class<Student> clazz11 = Student.class;
        //通过字节码对象获取目标类中的成员方法们
        Method[] ms = clazz11.getMethods();
        //打印查看方法数组对象的所有内容
        System.out.println(Arrays.toString(ms));
        //使用高效for循环遍历数组
        for(Method m:ms){
            System.out.println(m);
            System.out.println(m.getName());//通过遍历到的方法对象,查看方法名
            //通过遍历到的方法对象,查看方法的参数类型,返回值为数组
            System.out.println(Arrays.toString(m.getParameterTypes()));
        }
    }

    //4.通过单元测试方法,获取目标类Student中的所有构造方法们
    @Test
    public void getCons(){
        //获取字节码对象
        Class<?> clazz222 = new Student().getClass();
        //通过字节码对象,获取目标类中的构造方法们
        Constructor<?>[] c = clazz222.getConstructors();
        //遍历构造方法数组,分别打印方法名和方法参数
        for(Constructor n:c){
            System.out.println(n.getName());
            System.out.println(Arrays.toString(n.getParameterTypes()));
        }
    }

    //5.通过反射,创建目标类的对象
    @Test
    public void getObject() throws Exception {
        //获取字节码对象
        Class<?> clazz = Student.class;
        //通过反射技术创建目标类的对象
        /*反射创建对象方案1:通过触发目标类中的无参构造创建对象*/
        Object o = clazz.newInstance();
        System.out.println(o);

        /*反射创建对象方案2:通过触发目标类的全参构造创建对象*/
        /*1.先获取指定的构造方法对象
        * 注意需要指定构造方法的参数,并且传入的是.class字节码对象*/
        //获取目标类中指定的那个全参构造对象
        Constructor<?> cs = clazz.getConstructor(String.class,int.class);
        System.out.println(cs);
        //通过获取到的构造方法:创建对象+给对象的所有属性赋值
        Object o1 = cs.newInstance("赵六", 6);
        System.out.println(o1);

        //需求:触发Student类中第3个构造方法,创建对象o3
        //先获取到指定的构造方法对象
        Constructor<?> cs11 = clazz.getConstructor(String.class, int.class, int.class);
        //使用这个刚获取到的构造方法对象,帮我们创建Student类的对象
        Object o12 = cs11.newInstance("王五", 6, 18);
        System.out.println(o12);
    }
}


class Student {
    //1.定义成员变量
    private String name;
    public int age;
    public int age2;

    //2.给被封装的属性提供get与set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    //3.添加本类的无参构造与全参构造
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Student(String name, int age,int age2) {
        this.name = name;
        this.age = age;
        this.age2 = age2;
    }
    //4.提供本类的普通方法
    public void play(){
        System.out.println("今天大结局,放学后我要写1w行代码玩玩~");
    }
    public void eat(int n){
        System.out.println("今天要吃"+n+"道好菜~");
    }
    //5.为了查看对象时打印具体的类型 属性 属性值 所以重写toString()方法


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

暴力反射:

package cn.tedu.reflection;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**本类用于测试暴力反射*/
public class TestReflection22 {
    /*1.通过暴力反射获取与操作属性*/
    @Test
    public void getFie2() throws Exception {
        //获取字节码对象
        Class<Person> clazz = Person.class;
        //获取指定的私有属性,方法参数传入的就是指定的属性的名字
        Field field = clazz.getDeclaredField("name");
        //打印查看刚刚获取到的属性对象
        System.out.println(field);
        //根据刚刚获取到的属性对象,进一步查看属性的各种信息
        System.out.println(field.getType());//class java.lang.String
        System.out.println(field.getType().getName());//java.lang.String

        /*设置属性的值*/
        //1.需要指定到底是哪个对象的name属性设置值,没有对象就创建对象
        Person p1 = clazz.newInstance();//利用反射,触发无参构造创建对象
        /*暴力反射,需要设置私有可见的权限!!!*/
        field.setAccessible(true);
        //通过字段对象Field给刚刚创建好的对象p1设置属性值
        //用到的方法是:field.set(0,n),o是指具体给哪个对象的属性设置值,n是指具体的值是什么
        field.set(p1,"海绵宝宝");
        //field.get(o);o是指具体查看哪个对象的name属性值
        System.out.println(field.get(p1));
    }

    //定义单元测试方法,利用暴力反射操作Person类中的私有属性age
    @Test
    public void getFie3() throws Exception {
        //获取字节码对象
        Class<Person> clazz = Person.class;
        Field age = clazz.getDeclaredField("age");
        System.out.println(age);
        System.out.println(age.getType().getName());

        //创建对应的对象
        Person p2 = clazz.newInstance();
        age.setAccessible(true);
        age.set(p2,66);
        System.out.println(age.get(p2));
    }

    /*2.通过暴力反射获取与使用方法*/
    @Test
    public void getFunction2() throws Exception {
        //获取字节码对象
        Class<?> classzz = Person.class;
        //通过暴力反射获取指定的私有方法
        /*getDeclaredMethod(n,x,y,z....)
        * n:指要获取的指定方法的方法名
        * x,y,z...可变参数,是指指定方法的参数类型,注意传入的是字节码对象.class*/
        Method method = classzz.getDeclaredMethod("save", int.class, String.class);
        //System.out.println(method);//查看是否获取到方法对象
        //没有对象,就通过反射创建类的对象
        Object p3 = classzz.newInstance();
        method.setAccessible(true);
        /*invoke(o,x,y,z...)表示通过反射执行方法
        * o:指要给哪个对象执行上面获取到的方法,如save()
        * x,y,z...执行方法时需要传入的参数,如save()方法的参数*/
        method.invoke(p3,999,"感冒灵");
    }

    @Test
    public void getFunction3() throws Exception {
        //获取字节码对象
        Class<?> clazz = Person.class;
        //获取私有方法
        Method me = clazz.getDeclaredMethod("update");
        //通过字节码对象创建类的对象
        Object p5 = clazz.newInstance();
        //设置操作权限
        me.setAccessible(true);
        //执行方法
        me.invoke(p5);
    }



    @Test
    public void getFunction4() throws Exception {
        //获取字节码对象
        Class<?> clazz = new Person().getClass();
        //获取私有方法对象
        Method me = clazz.getDeclaredMethod("add", String.class);
        me.setAccessible(true);
        //创建类对象
        Object p6 = clazz.newInstance();
        //调用方法
        me.invoke(p6,"好的");
    }
}


class Person {
    //1.提供私有属性
    private String name;
    private int age;
    //2.提供私有方法
    private void save(int n,String s){
        System.out.println("save方法"+n+s);
    }
    private void update(){
        System.out.println("update方法");
    }
    private void add(String str){
        System.out.println("add方法"+str);
    }
}

日期类Date的入门:

package cn.tedu.date;
import java.text.SimpleDateFormat;
import java.util.Date;
/**本类用于日期类Date的入门*/
public class TestDate {
    public static void main(String[] args) {
        //创建日期类对象,包含当前日期
        Date d1 = new Date();
        System.out.println(d1);
        //创建一个日期转换工具类的对象,用来转换日期的格式
        SimpleDateFormat sdf = new SimpleDateFormat();
        //使用工具转换日期
        String s = sdf.format(d1);
        System.out.println(s);
        //还可以自定义格式来转换日期
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String s2 = sdf2.format(d1);
        System.out.println(s2);
    }
}

日期类:LocalDate

package cn.tedu.jbbc;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Set;
public class TestCalendar {
    public static void main(String[] args) {
        LocalDate now = LocalDate.now();
        System.out.println(now);
        LocalTime now1 = LocalTime.now();
        System.out.println(now1);
        LocalDateTime now2 = LocalDateTime.now();
        System.out.println(now2);
//        LocalTime.now(ZoneId.of())
        Set<String> a = ZoneId.getAvailableZoneIds();
        for(String i:a){
            System.out.println(i);
        }
        LocalTime now3 = LocalTime.now(ZoneId.of("Hongkong"));
        System.out.println(now3);
    }
}
举报

相关推荐

0 条评论