JAVA学习笔记(下)

多线程

创建多线程一共有四种方式!!:

  • 继承Thread类
  • 实现Runndable接口
  • 实现Callable接口
  • 使用线程池

继承Thread类

/*
    创建线程的方式:方式一:继承Thread类
    1.创建一个继承于Thread类的紫烈
    2.重写Thread类的run() -->将此线程的操作声明在run()中
    3.创建Thread类子类的对象
    4.通过此对象调用start()
 */
    //1.创建一个Thread类的子类
class MyThread extends Thread{
    //2.重写Thread类中的run()

    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if (i%2==0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类子类的对象
        MyThread myThread = new MyThread();
        //4.通过此对象调用start():1.启动当前线程 2.调用当前线程的run()
        myThread.start();
        System.out.println("hello");
        //也可以写匿名子类
        new Thread(){
            @Override
            public void run() {

            }
        }.start();
    }
}

线程常用方法

/**
 * 测试Thread类中的常用方法:
 * 1.start():启动当前线程,调用当前线程的run()
 * 2.run():通常需要重写Thread类中的此方法,将创建的线程执行的操作声明在此方法中
 * 3.currentThread():静态方法,返回执行当前代码的线程
 * 4.getname():获取当前线程的名字
 * 5.setname():设置当前线程的名字
 * 6.yield():释放当前CUP的执行权
 * 7.join():在线程A中调用线程B的join()方法,此时线程A就进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态
 * 8.stop():已过时。当执行此方法时,强制结束当前线程。
 * 9.sleep(long millitime):让当前线程睡眠指定millitime毫秒数,应用场景:新年倒计时的时候
 * 10.isAlive():判断当前线程是否还存活
 *
 * 1.线程的优先级:
 * MAX_PRIORITY:10
 * MIN_PRIORITY:1
 * NORM_PRIORITY:5  -->默认的优先级
 * 2.如何获取或设置当前线程的优先级:
 * getPriority():获取线程的优先级
 * setPriority(int p)设置线程的优先级
 *      说明:高优先级的线程要抢占低优先级的cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有高优先级的执行完后,低优先级的才执行
 */
class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
            }
            if (i%20==0){
                this.yield();
            }
        }
    }
}
public class ThreadMethodTest {
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread();
        h1.setName("线程一");
        //设置分线程的优先级
        h1.setPriority(Thread.MAX_PRIORITY);
        h1.start();
        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        for (int i = 0; i <100 ; i++) {
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
            }
            if (i==20){
                try {
                   h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
        }
        System.out.println(h1.isAlive());
    }
}

实现Runnable接口

/**
 * 创建多线程的方式二:实现Runnable接口
 * 1.创建一个实现Runnable接口的类
 * 2.实现类去实现Runnable中的抽象方法:run()
 * 3.创建实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Threa类的对象,调用start()方法
 */
class MThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if (i%2==0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        Thread thread = new Thread(mThread);
        thread.start();
    }
}

比较创建多线程的两种方式。

开发中:优先选择实现Runnable接口的方式

  • 原因:1.实现方式没有类的单继承性的局限性

    ​ 2.实现的方式更适合来处理多个线程,有共享数据的情况

  • 联系:Thread()类也实现了Runnable接口

  • 相同点:都需要重写run()方法

线程生命周期

image-20200730111754087

线程安全问题

/**
 *例子常见三个窗口卖票,总票数为100,使用Runnable接口
 * 1.问题:卖票过程中,出现了重票,错票,-->出现了线程安全问题
 * 2.问题出现的原因:当某个线程操作车票的过程当中,尚未完成时,其他线程参与进来,也操作了车票。
 * 3.如何解决:当一个线程A操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket的时候,
 *   其他线程才可以开始操作ticket,即使线程A出现了阻塞,也不能被改变。
 * 4.在Java中,我们通过同步机制,来解决线程的安全问题
 * 方式一:同步代码块
 *  synchronized(同步监视器){
 *      //需要被同步的代码
 *  }
 *  说明 :1.操作共享数据的代码,即为需要同步的代码
 *        2.共享数据:多个线程共同操作的数据,比如本问题中的ticket
 *        3.同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。
 *          要求:多个线程必须要共用通一把锁。
 *        补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this来充当同步监视器。
 *              在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,可以考虑使用当前类来使用同步监视器
 * 方式二:同步方法
 *      如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法生命为同步的。
 *      总结:1.同步方法仍然涉及同步监视器,只是不需要我们显示声明。
 *           2.非静态的同步方法中,同步监视器是:this
 *             静态的同步方法,同步监视器是:当前类本身。 因为静态方法中不能使用this
 *
 *
 *
 * 5.同步的方式,解决了线程安全问题。 ---好处
 *      操作同步代码时,只能有一个线程参与,其他想等待。相当于是一个单线程的过程,效率低。 ---局限性
 */

使用同步代码块解决线程安全问题

class window1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {

        while (true){
            synchronized (obj){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+":卖出的第"+ticket);
                ticket--;
            }else {
                break;
            }
        }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        window1 window1 = new window1();
        Thread thread1 = new Thread(window1);
        Thread thread2 = new Thread(window1);
        Thread thread3 = new Thread(window1);
        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

把懒汉式修改为线程安全的

/**
 * 使用线程全机制,将线程安全的懒汉式,改写为线程安全的
 */
public class BankTest {

}
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static Bank getInstance(){
        if (instance==null){
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
    }
        return instance;
    }
}

死锁

/**
 * 线程死锁问题
 * 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
 *   都在等待对方放弃自己需要的同步资源,就形成了死锁。
 *
 * 2.说明:
 * 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 * 2)我们使用同步时,要避免死锁
 *
 *
 * @author ruirui
 * @date 2020/7/30 - 14:13
 */
//代码解释,第一个线程先用a后用B,第二个线程先用B后用A。再加上睡眠就容易出现死锁的状况。
public class ThreadDeadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("1");
                    s2.append("2");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("3");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("1");
                    s2.append("2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("3");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

Lock锁

/**
 * 解决线程安全问题的方式三:Lock ---JDK5.0新增
 *  1.面试题:synchronized 与 Lock 的异同?
 *  相同:二者都可以解决线程安全问题
 *  不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
 *       Lock需要手动的启动同步lock(),同步结束后也要手动的实心unlock()
 * 2.面试题 如何解决线程安全问题?有几种方式。
 *  三种:同步代码块,同步方法,lock锁
 *
 * @author ruirui
 * @date 2020/7/30 - 15:23
 */
class window2 implements Runnable{
    private int ticket = 100;
    //实例化一个ReentrantLock
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+": 售票,票号为:"+ticket);
                ticket--;
            }else{ break;}
            lock.unlock();
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        window2 w2 = new window2();
        Thread t1 = new Thread(w2);
        Thread t2 = new Thread(w2);
        Thread t3 = new Thread(w2);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

线程通信

/**
 * 线程通信的例子:使两个线程打印1-100。线程1,线程2,交替打印。
 * 涉及到的三个方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 * notify():一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程被wait(),就会唤醒优先级最高的那个
 * notifyAll():一旦执行此方法,就会唤醒所有被wait()的一个线程
 *说明:
 * 1.wait(),notify(),notifyAll():三个方法,必须使用在同步代码块或同步方法中。
 * 2.wait(),notify(),notifyAll()三个方法的调用者,必须是同步代码块或同步方法中的同步监视器。
 *   否则会出现异常。
 * 3.wait(),notify(),notifyAll()三个方法是定义在,Object类中的。
 *
 * @author ruirui
 * @date 2020/7/30 - 16:09
 */
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}
class Number implements Runnable{
    private int num = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                notify();
                if (num<=100){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else break;
            }
        }
    }
}

面试题:Sleep方法和Wait方法的异同

相同点: 一旦执行,都可以使当前线程进入阻塞状态。

不同点:1)两个方法声明的位置不同。Thread类中声明的sleep()方法,Object类中声明的wait()方法

​ 2)调用的范围不同:sleep()可以在任何需要的场景下调用。wait()必须在同步代码块或同步方法中。

​ 3)关于是否释放同步监视器的问题:如果两个方法都是用在同步代码块或同步方法中,sleep()不会释放 同步监视器,wait()会释放同步监视器。

经典问题:生产者消费者问题

/**线程通信的应用:经典例题:生产者/消费者问题
 *
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图产生更多的产品,店员会
 * 叫生产者听一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会
 * 告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
 *
 * 分析:
 * 1.是否是多线程问题?是,生产者线程,消费者线程
 * 2.是否有共享数据的问题?是,店员(产品)
 * 3.如何解决线程安全问题?同步机制,有三种方法
 * 4.是否涉及线程的通信?是
 *
 *
 * @author ruirui
 * @date 2020/7/30 - 16:37
 */
class Clerk{
    private int productCount = 0;
    //  生产产品
    public synchronized void produceProduct() {
        if (productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            notify();//只要生产者生产了一个产品了,消费者才能消费
        }else {
        //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void consumeProduct() {
        if (productCount>0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            notify();//消费者消费产品了,生产者才能继续生产
        }else {
        //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Producter extends Thread{//生产者
    private Clerk clerk;
    public Producter(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()+":开始生产产品……");
        while (true) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
class Consumer extends Thread{
    private Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()+":开始消费产品……");
        while (true) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producter p1 = new Producter(clerk);
        p1.setName("生产者1");
        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        p1.start();
        c1.start();
    }
}

实现Callable接口实现多线程

/**
 * 创建线程的方式三:实现Callable接口。 ---JDK5.0新增
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口更强大?
 * 1.call()方法是可以有返回值的
 * 2.call()方法可以抛出异常,被外面的操作捕获,获取异常信息
 * 3.call()是支持泛型的
 *
 */
//1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程执行的操作声明在call()方法中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <=100 ; i++) {
            if (i%2==0){
                System.out.println(i);
                sum+=i;
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象,传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);//FutureTask实现了runnable
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
            //6.获取Callable中的call()方法的返回值
            //get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
            Object o = futureTask.get();
            System.out.println("总和为"+o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

线程池

/**
 * 创建线程的方式四:使用线程池
 * 好处:
 * 1.提高响应速度(减少创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
 * 3.便于线程管理
 *  corePoolSize:核心池的大小
 *  maximumPoolSize:最大线程数
 *  keepAilveTime:线程没有任务时,最多保持多长时间后终止
 */
class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <100 ; i++) {
            if (i%2==0){
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
    }
}
class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <100 ; i++) {
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //设置线程池的属性
        

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口的实现类的对象
        service.execute(new NumberThread());//适用于Runnable
        service.execute(new NumberThread1());
//        service.submit(Callbele callable);适合适用于Callable
        //关闭连接池
        service.shutdown();
    }
}

注解

/**
 * 理解Annotation:
 * jdk 5.0新增
 *1.作用在代码的注解是:
 * @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
 * @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
 * @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
 *
 * 2.如何自定义注解:参照@SuppressWarnings定义
 * ①注解生命为:@interface
 * ②内部定义成员,通常使用value表示
 * ③可以指定成员的默认值,使用默认default定义
 * ④如果自定义注解没有成员,表明是一个标识作用。
 *
 * 如果注解有成员,在使用注解时,需要指明成员的值。
 * 自定义注解必须配上注解的信息处理流程(通过反射)才有意义
 * 自定义注解通常都会指明两个元注解:Retention,Target
 *
 * 3.jdk的4种元注解:对现有的注解进行解释说明的注解
 * @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。SOURCE\CLASS(默认行为)\RUNTIME,只有声明为RUNTIME声明周期的注解,
 * 才能通过反射获取。
 * @Target - 标记这个注解应该修饰哪种 Java 成员。
 * ********************出现频率较低*************************
 * @Documented - 标记这些注解是否包含在javadoc用户文档中.
 * @Inherited - 被它修饰的Annotaton将具有继承性(默认 注解并没有继承于任何子类)
 *
 * 4.通过反射获取注解信息,
 *
 * 6.jdk 8 中注解新特性:可重复注解,类型注解
 *  6.1 可重复注解:①在MyAnnotation上声明@Repeatable(),成员值为MyAnnotations.class
 *                 ②MyAnnotation的Target和Retention和MyAnnotations的相同。
 *
 *  6.2 类型注解:
 *  ElementType.TYPE_PARAMETER 表示改注解能写在类型变量的声明语句中(如:泛型声明).class Generic<@MyAnnotation T>{}  在@MyAnnotation注解的@target中加入这个类型就可以使用
 *  ElementType.TYPE_USE 表示改注解能写在使用类型的任何语句中。
 *
 *
 */
@SuppressWarnings("hello")
public class AnnotationTest {

}

class Generic<@MyAnnotation T>{

}
//注解类
@Target({ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello";
}

反射

谈谈你对Class类的理解:Class实例对应着加载到内存的一个运行时类。

没明白的问题:类的加载器

获取Class类的实例

/**
 * Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期javac,借助Reflection API获得任何类的内部信息,并能直接操作,任意对象的内部属性和方法。
 */
public class ReflectTest {
    //反射之前对于person类的操作
    @Test
    public void test1(){
        //1.创建Persion类的对象
        Person person =new Person("梁瑞",21);
        //2.通过对象调用器内部的属性和方法
        person.age = 10;
        System.out.println(person.toString());
        //在Person类外部,不可以通过Persion类的对象调用其内部私有结构。
        //比如私有的内部属性,私有方法,私有构造器
    }
    //反射之后,对Persion的操作
    @Test
    public void test2() throws Exception {
        //1.通过反射,创建Person类对象
        Class clazz = Person.class;
        Constructor con = clazz.getConstructor(String.class, int.class);
        Object tom = con.newInstance("Tom", 12);
        System.out.println(tom.toString());
        //2.通过反射,调用对象指定的属性,方法。
        Field age = clazz.getDeclaredField("age");
        age.set(tom,10);
        System.out.println(tom.toString());
        //3.调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(tom);
        //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。
        Constructor con1 = clazz.getDeclaredConstructor(String.class);
        con1.setAccessible(true);
        Object p1 = con1.newInstance("ruirui");
        System.out.println(p1);

        //调用私有属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"Hanmeimei");
        System.out.println(p1);

        //调用私有方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        Object o = showNation.invoke(p1, "中国");
        System.out.println(o);
    }

    /*
        关于java.lang.Class类的理解
        1.类的加载过程:
        程序在经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),
        接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
        加载到内存中。此过程就成为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,
        就作为Class的一个实例。

        2.换句话说,Class的实例就是一个运行时类 .
        3.加载到内存中的内存时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
        来获取此运行时类。
     */

    //获取Class的实例方式(前三种方式需要掌握)
    @Test
    public void test3() throws ClassNotFoundException {
        //方式一、调用运行类的 .class
        Class<Person> clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二、通过运行时类的对象,调用.getClass()
        Person p1 = new Person();
        Class clazz = p1.getClass();  //getClass方法是在Object中声明的
        //方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.reflect.Person");

        System.out.println(clazz==clazz1);
        System.out.println(clazz==clazz3);
        //方式四:使用类的加载器 ClassLoader(了解)
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.reflect.Person");
        System.out.println(clazz4);
    }
}

Person类

public class Person {
    private String name;
    public int age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public void show(){
        System.out.println("你好,我是一个人");
    }
    private String showNation(String nation){
        System.out.println("我的国籍是: "+nation);
        return nation;
    }

}

类的加载器

/*
   了解类的加载器:1.引导类加载器:它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
                 2.扩展类加载器:它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
                 3.系统类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
                 4.除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
 */
public class ClassLoaderTest {
    @Test
    public void test1(){
        //对于已定义类,使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        //调用系统类加载器的getParent():获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);
        //调用扩展类加载器的getParent():无法获取引导类类加载器
        //引导类加载器主要负责加载Java的核心类库,无法加载自定义类
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);
    }

    /*
    Properties:用来读取配置文件.
     */
    @Test
    public void test2() throws IOException {
        Properties pro = new Properties();
        //读取配置文件的方式一
//        FileInputStream fis= new FileInputStream("F:\\kuangshencode\\JavaSE\\基础语法\\src\\com\\reflect\\jdbc.properties");
//        pro.load(fis);
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("com\\reflect\\jdbc.properties");//默认路径是在src下
        pro.load(is);
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");
        System.out.println(user+"   "+password);
    }
}

创建运行时类的对象(重要)

/**
 * 通过反射常见对应的运行时类的对象。
 */
public class NewInstanceTest {
    @Test
    public void test1() throws IllegalAccessException, InstantiationException {
        Class<Person> clazz  = Person.class;
        /*
            newInstance():调用此方法,创建对用的运行时类的对象。内部调用了运行时类的空参构造器。
            要想此方法正常创建运行时类的对象,要求:
            1.运行时类必须提供空参构造器
            2.空参构造器的访问权限得够,通常是public。
            在javabean中要求提供一个public的空参构造器。原因:
            1.便于通过反射,创建运行时类的对象
            2.便于子类继承运行时类时,默认调用super()时,保证父类有此构造器。
         */
        Person person = clazz.newInstance();
        System.out.println(person);
    }
    //反射的动态性
    @Test
    public void test2(){
        int i = new Random().nextInt(3);//0,1,2
        String classPath = "";
        switch (i){
            case 0:
                classPath = "java.lang.String";
                break;
            case 1:
                classPath = "java.util.Date";//java.sql.Date 没有空参构造器,所以会报错
                break;
            case 2:
                classPath = "com.reflect.Person";
                break;
        }
        try {
            Object instance = getInstance(classPath);
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*
    创建一个指定类的对象
    classPath:指定类的全类名
     */
    public Object getInstance(String classPath) throws Exception {
        Class clazz = Class.forName(classPath);
        return clazz.newInstance();
    }
    @Test
    public void test3() throws Exception {
        //通过newInstance每次获取的对象地址值都不一样,因为它是调用空参构造函数,相当于new了一个对象
        Class<Person> clazz = Person.class;
        Person person = clazz.newInstance();
    }
}

获取运行时类的完整结构

获取属性及其内部结构

@Test
public void test1(){
    Class<Person> clazz = Person.class;
    //获取属性结构
    //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    //getDecaredField():获取当前运行时类中的所有属性,不包含父类中的所有属性.
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);
    }
}
//权限修饰符 数据类型 变量名 = ..
@Test
public void test2(){
    Class<Person> clazz = Person.class;
    Field[] declaredFields = clazz.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        //1.获取权限修饰符
        int modifiers = declaredField.getModifiers();
        System.out.println();
        //2.数据类型
        Class type = declaredField.getType();
        System.out.println(type+"   "+Modifier.toString(modifiers));
        //3.变量名
        String name = declaredField.getName();
        System.out.println(name);
    }
}

获取运行时类的方法及其内部结构

/**
 * 获取运行时类的方法结构
 */
public class MethodTest {
    @Test
    public void test1(){
        Class<Person> clazz = Person.class;
        //getMethods():获取当前运行时类及其父类中生命为public权限的方法.
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println();
        //getDeclaredMethods():获取当前运行时类中声明的所有方法(不包含父类中的方法)
        Method[] methods1 = clazz.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }
    }
    /*
        @Xxx
        权限修修饰符  返回值类型  方法名(参数类型1 形参名1 。。。) throws xxxExceotion
     */
    @Test
    public void test2(){

        Class<Person> calzz = Person.class;
        Method[] m = calzz.getDeclaredMethods();
        for (Method method : m) {
            //1.获取方法声明的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
            //2.权限修饰符
            System.out.print(Modifier.toString(method.getModifiers())+"\t");

            //3.返回值类型
            Class returnType = method.getReturnType();
            System.out.print(returnType.getName()+"\t");

            //4.方法名
            System.out.print(method.getName());
            System.out.print("(");
            //5.形参列表
            Class<?>[] types = method.getParameterTypes();
            if (!(types==null&&types.length==0)){
                for (int i = 0; i <types.length ; i++) {
                    if (i==types.length-1) {
                        System.out.print(types[i].getName()+"args_"+i);
                        break;
                    }
                    System.out.print(types[i].getName()+"   args_"+i+",");

                }
                }
            System.out.print(")");
            //6.抛出的异常
            Class<?>[] exceptionTypes = method.getExceptionTypes();
            if (exceptionTypes.length>0){
                System.out.print("throws");
                for (int i = 0; i <exceptionTypes.length ; i++) {
                    if(i==exceptionTypes.length-1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }
                    System.out.print(exceptionTypes[i].getName()+",");
                }
            }
            System.out.println();
            }

        }
    }

获取构造器结构

/*
    获取构造器结构
 */
@Test
public void test1(){
    Class<Person> clazz = Person.class;
    //获取当前运行时类中,声明为public的构造器
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println(constructor);
    }
    //getDeclaredConstructors():获取当前运行时类中的所有构造器
    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor);
    }
}

获取运行时类的其他结构(接口,父类)

    /*
        获取构造器结构
     */
    @Test
    public void test1(){
        Class<Person> clazz = Person.class;
        //获取当前运行时类中,声明为public的构造器
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        //getDeclaredConstructors():获取当前运行时类中的所有构造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
    }
    @Test
    public void test2(){
        //获取运行时类的父类
        Class<Person> clazz = Person.class;
        Class<? super Person> superclass = clazz.getSuperclass();
        System.out.println(superclass);

    }
    @Test
    public void test3(){
        //获取运行时类带泛型的父类
        Class<Person> clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }
    @Test
    public void test4(){
        //获取运行时类带泛型父类的泛型
        Class<Person> clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType type = (ParameterizedType) genericSuperclass;
        Type[] actualTypeArguments = type.getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument);
        }
    }
    @Test
    public void test5(){
        //获取运行时类实现的接口
        Class<Person> clazz = Person.class;
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println(anInterface);
        }
    }
    @Test
    public void test6(){
        //获取运行时类所在的包
        Class<Person> clazz = Person.class;
        Package aPackage = clazz.getPackage();
        System.out.println(aPackage);
    }
    @Test
    public void test7(){
        Class<Person> clazz = Person.class;
        //获取运行时类声明的注解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

调用运行时类的特定结构

/**
 * 调用运行时类的指定结构:属性,方法,构造器
 */
public class ReflectionTest {
    /*

     */
    @Test
    public void test1() throws Exception {

        Class<Person> clazz = Person.class;
        //创建运行时类的对象
        Person person = clazz.newInstance();
        //获取指定属性:要求运行时类的属性为public的
        //通常不采用此方法
        Field id = clazz.getField("id");
        //设置当前属性的值  set():参数1 设置指明那个对象的属性  参数2:将属性设置为多少
        id.set(person,1001);
        /*
            获取当前属性值
            get():参数1:获取哪个对象的当前属性
         */
        int pid = (int)id.get(person);
        System.out.println(pid);
    }
    //如何操作运行时类指定的属性
    @Test
    public void test2() throws Exception {
        Class<Person> clazz = Person.class;
        Person person = clazz.newInstance();
        //getDeclaredField(String fieldName):获取运行时类的指定变量名的属性
        Field name = clazz.getDeclaredField("name");
        //2.保证当前属性是可访问的
        name.setAccessible(true);
        //3.获取,设置指定对象的此属性值
        name.set(person,"Tom");
        System.out.println(name.get(person));
    }
    /*
     如何操作指定的运行时类的方法 --  需要掌握
     */
    @Test
    public void testMethod() throws Exception {
        Class<Person> clazz = Person.class;
        System.out.println(clazz);
        //创建运行时类的对象
        Person person = clazz.newInstance();
        /*1.获取指定的方法
        getDeclaredMethod():参数1 :指明获取方法的名称  参数 2:指明获取方法的形参列表。
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);
        //调用invoke():参数1:方法的调用者   参数2:给形参赋值的实参
        //invoke()的返回值即为对应类中调用的方法的返回值。
        Object result = show.invoke(person, "CHN");
        System.out.println(result);
        System.out.println("*******************如何调用静态方法***********************");
        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用运行时类中的方法没有返回值,则此invoke()返回null
        showDesc.invoke(Person.class);
    }
    /*
        如何调用运行时类指定的构造器
     */
    @Test
    public void twst3() throws Exception {
        Class<Person> clazz = Person.class;
        /*
            1.获取指定的构造器
            getDeclaredConstructor():参数,指明构造器的参数列表
         */
        Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
        //2.保证此构造器是可访问的
        declaredConstructor.setAccessible(true);
        //3.调用此构造器创建运行时类的对象
        Person tom = declaredConstructor.newInstance("Tom");
        System.out.println(tom);
    }
}

代理模式

静态代理:代理类和被代理类在编译期间就被确定下来,不利于程序的扩展,每一个代理类只为一个接口服务,这样一来程序开发中必然产生过多的代理。

/*
    接口的应用:代理模式
 */
public class NetWorkTest {
    public static void main(String[] args) {
        Server server = new Server();
        ProxyServer proxyServer = new ProxyServer(server);
        proxyServer.browse();
    }
}
interface NetWork{
    public void browse();
}
//被代理类
class Server implements NetWork{
    @Override
    public void browse() {
        System.out.println("真实的服务器访问网络");
    }
}
//代理类
class ProxyServer implements NetWork{
    private NetWork work;
    public ProxyServer(NetWork work){
        this.work = work;
    }
    //
    public void check(){
        System.out.println("联网之前的检查工作");
    }
    @Override
    public void browse() {
        check();
        work.browse();
    }
}

动态代理:

/*
    动态代理举例
 */
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法。
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");
        System.out.println("******************************************");
        NikeClothFactory nike = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory)ProxyFactory.getProxyInstance(nike);
        proxyInstance1.produceCloth();
    }
}
interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{

    public String getBelief() {
        return "I BELIEVE I CAN FLY!";
    }

    public void eat(String food) {
        System.out.println("我喜欢吃"+food);
    }
}
/*
 要想生成动态代理,需要解决的问题?
 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
 问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
 */

class ProxyFactory{
    //调用此方法创建一个代理累的对象
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        MyInvocationHandler invocationHandler = new MyInvocationHandler();
        invocationHandler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),invocationHandler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//使用被代理类的对象进行赋值
    public void bind(Object obj){
        this.obj=obj;
    }
    //当我们通过代理类的对象,调用方法A时,就会调用如下方法:invoke()
    //将被代理类执行的方法A的功能就声明在invoke()中
    //参数1:代理类的对象   参数2:代理类的方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类调用的方法
        Object retureValue = method.invoke(obj, args);
        //上述方法的返回值就作为invoke()方法的返回值。
        return retureValue;
    }
}

使用动态代理理解AOP

/*
    动态代理举例
 */
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法。
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");
        System.out.println("******************************************");
        NikeClothFactory nike = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory)ProxyFactory.getProxyInstance(nike);
        proxyInstance1.produceCloth();
    }
}
interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{

    public String getBelief() {
        return "I BELIEVE I CAN FLY!";
    }

    public void eat(String food) {
        System.out.println("我喜欢吃"+food);
    }
}
/*
 要想生成动态代理,需要解决的问题?
 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
 问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
 */

class ProxyFactory{
    //调用此方法创建一个代理累的对象
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        MyInvocationHandler invocationHandler = new MyInvocationHandler();
        invocationHandler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),invocationHandler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//使用被代理类的对象进行赋值
    public void bind(Object obj){
        this.obj=obj;
    }
    //当我们通过代理类的对象,调用方法A时,就会调用如下方法:invoke()
    //将被代理类执行的方法A的功能就声明在invoke()中
    //参数1:代理类的对象   参数2:代理类的方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        HumanUtil humanUtil = new HumanUtil();
        humanUtil.method1();
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类调用的方法
        Object retureValue = method.invoke(obj, args);
        humanUtil.method2();
        //上述方法的返回值就作为invoke()方法的返回值。
        return retureValue;
    }
}
class HumanUtil{
    public void method1(){
        System.out.println("===============通用方法一=================");
    }
    public void method2(){
        System.out.println("==================通用方法二======================");
    }
}
posted @ 2020-10-12 21:34  长得黑的程序员  阅读(90)  评论(0编辑  收藏  举报