JavaSE

JavaSE

==与equals()方法的区别

==equals()
基本数据类型值是否相等不适用
引用数据类型地址值是否相等对于Object类下的比较的是地址,对于一些特定的类,如String,Date,File等,它们都重写了equals()方法比较的是字面内容

数组陷阱

char型数组返回的不是地址值

public class ArrayPrintTest {

public static void main(String[] args) {

	int[] arr = new int[] { 1, 2, 3 };
	char[] arr1 = new char[] { 'a', 'b', 'c' };
	System.out.println(arr);
	System.out.println(arr1);
}
    }

打印内容为abc

static关键字

1、static:静态的

2、static可以用来修饰,属性,方法,代码块,内部类(不能修饰构造器)

3、使用static修饰属性:静态变量(或类变量)

属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)

实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。

当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改

静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致

其他对象调用此静态变量时,是修改过了的。

static修饰属性的其他说明。

①静态变量随着类的加载而加载。可以通过类.静态变量的方式进行调用

②静态变量的加载要早于对象的创建

③由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

类变量实例变量
yesno
对象yesyes

静态属性举例

4、使用static修饰方法:静态方法

①随着类的加载而加载,可以通过"类.静态方法"的方式进行调用

静态方法非静态方法
yesno
对象yesyes

③静态方法中只能调用静态的方法或属性

非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性

5、static注意点:

在静态的方法内,不能使用this关键字,super关键字

6、开发中,如何确定一个属性是否要声明为static?

属性可以被多个对象所共享的,不会随着对象的不同而不同的。

开发中,如何确定一个方法是否要声明为static?

操作静态属性的方法通常设置为静态的

工具类中的方法,习惯上声明为static的。

单例设计模式

定义:采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

饿汉式

public class PersonTest{
    Person p1 = Person.getPerson;
    Person p2 = Person.getPerson;
    System.out.println(p1 == p2);
}
class Person{
    //1、私有化构造器
    private Person(){
        
    }
    //2、内部创建类的对象
    //4、对象声明为静态
    private static Person person = new Person();
    //3、公共的返回对象的方法
    public static Person getPerson(){
        return person;
    }
}

懒汉式

public class PersonTest{
    
}
class Person{
    private Person(){
        
    }
    private static Person person = null;
    
    public static Person getPerson(){
        if(person == null){
             return new Person();
        }
        return person;
}
饿汉式懒汉式
对象加载时间
线程安全安全目前的写法不安全

代码块

1、作用:用来初始化类,对象

2、代码块如果有修饰的话,只能使用static

3、分类:静态代码块和非静态代码块

4、静态代码块

内部可以有输出语句

随着类的加载而执行,并且只会执行一次

5、非静态代码块

内部可以有输出语句

随着对象的创建而执行

每创建一个对象,就执行一次非静态代码块

作用:可以在创建对象时对对象的属性等进行初始化

对属性可以复制的位置:

  • ①默认初始化
  • ②显示初始化
  • ③构造器初始化
  • ④有了对象后,通过"对象.属性"或"对象.方法"的方式,进行赋值
  • ⑤在代码块中赋值

final关键字

final:最终的

1、final可以用来修饰的结构:类,方法,变量

2、final用来修饰一个类:这个类不能被其他类继承。如String类,System类,StringBuffer类

3、final用来修饰的方法:称为最终方法,不能够被重写。如Object类中的getClass();

4、final用来修饰变量:称为常量

  • final修饰属性:可以考虑赋值的位置有:显式初始化,代码块中初始化,构造器中初始化
  • final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。

static final 用来修饰属性:全局常量,

static final 用来修饰方法:

abstract关键字

abstarct:抽象的

abstract修饰类:抽象类

此类不能实例化

抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)

开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作

abstarct修饰方法:抽象方法

只有方法的声明,没有方法体

包含抽象方法的类一定是抽象类。反之,抽象类中可以没有抽象方法。

若子类重写了父类中的所有的抽象方法后,此子类方可实例化

若子类没有重写了父类中的所有的抽象方法后,则此子类也是一个抽象类,需要使用abstract修饰

注意点:

1、abstract不能用来修饰,属性,构造器等结构

2、abstract不能用来修饰私有方法、静态方法、final的方法,final的类

接口(interface)的使用

1、接口使用interface来定义

2、Java中,接口和类是并列的两个结构

3、如何定义接口:定义接口中的成员

  • JDK7及以前:只能够定义全局常量和抽象方法

    全局常量:public static final的,但是书写时,可以省略不写

    抽象方法:public abstract的

  • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

4、接口中不能定义构造器!意味着接口不可以实例化

5、Java开发中,接口通过让类去实现(implements)的方式来使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。如果实现类没有覆盖接口中所有的抽象方法。则此实现类仍为一个抽象类。

6、Java类可以实现多个接口 ---->弥补Java单继承性的局限性的问题

JDK8,除了定义全局常量和抽象方法之外,还可以定义静态方法,默认方法

抽象类和接口的共同点和区别

抽象类接口
相同点不能实例化,都可以被继承不能实例化,都可以被继承
不同点有构造器,单继承。只能修饰类或者方法不能声明构造器,多继承。JDK7及以前,能修饰全局常量和抽象方法。JDK8,还能修饰静态方法和默认方法。

代理模式

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{

	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("联网之前的检查工作");
	}

	public void browse() {
		check();
		work.browse();
	}
	
}

内部类

1、Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

2、内部类的分类:成员内部类 (静态、非静态)vs 局部内部类(方法内,代码块内,构造器内)

3、成员内部类:

​ 一方面,作为外部类的成员

调用外部类的结构

可以被static修饰

​ 另一方面,作为一个类

类内可以定义属性,方法,构造器等

可以被final修饰,表示此类不能被继承

可以被abstract修饰

多线程

多线程的创建,方式一:继承于Thread类

1、创建一个继承于Thread类的子类

2、重写Thread类的run()方法 —>将此线程执行的操作声明在run()中

3、创建Thread类的子类的对象

4、通过此对象调用start()方法

例:遍历100以内所有的偶数

public class ThreadTest {

	public static void main(String[] args) {
		//3、创建Thread类的对象
		MyThread t1 = new MyThread();
		//4、通过此对象调用start()
		t1.start();
        for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	
}
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
	//2、重写Thread类的run()
	public void run(){
		for (int i = 0; i < 100; i++) {
			if (i % 2 == 0) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

我们不能通过直接调用run()的方式启动线程

Thread类中的常用方法

方法名作用
start()启动当前线程:调用当前线程的run()
run()通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread()静态方法,返回执行当前代码的线程
getName()获取当前线程的名字
setName()设置当前线程的名字
yield()释放当前CPU的执行权
join()在线程A中调用线程B的join(),此时线程A就进入了阻塞状态。直到线程B完全执行完以后,线程A才结束阻塞状态。
stop()已过时。当执行此方法时,强制结束当前线程。
sleep(long millitime)让当前线程"睡眠"指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态。
isAlive判断当前线程是否存活

线程的优先级

1、MAX_PRIORITY:10

​ MIN_PRIORITY:1

​ NORM_PRIORITY:5 ---->默认优先级

2、如何获取和设置当前线程的优先级:getPriority():获取线程的优先级,setPriority():设置线程的优先级

高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才被执行。

/**
 * 目前存在线程不安全问题
 * 创建三个窗口,总票数为100张
 */
class Window extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                System.out.println(getName() + ":卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

多线程的创建,方式二:实现Runnable接口

1、创建一个实现了Runnable接口的类

2、实现类去实现Runnable中的抽象方法

3、创建实现类的对象

4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5、通过Thread类的对象调用start()

/**
 * 实现多线程的方式二:实现Runnable接口
 */
public class ThreadTest1 {

    public static void main(String[] args) {
        //3、创建实现类的对象
        MThread mThread = new MThread();
        //4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //5、通过Thread类的对象调用start()
        t1.setName("线程1");
        /**
         * ①启动线程
         * ②调用当前线程的run()---->调用了Runnable类型的target
         */
        Thread t2 = new Thread(mThread);
        t2.setName("线程2");

        t1.start();
        t2.start();

    }

}
//1、实现Runnable接口
class MThread implements Runnable{
    int count = 0;
    //2、重写run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.print(Thread.currentThread().getName() + ":" + i + " ");
                count ++;
                if (count % 5 == 0){
                    System.out.println();
                }
            }
        }
    }
}
/**
 * 实现Runnable接口的方式
 */
class Window1 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true){
            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 t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

比较创建线程的两种方式

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

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

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

联系:public class Thread implements Runnable

相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAB8xhp0-1610351481404)(C:\Users\ASUS\Desktop\ssm\多线程\QQ截图20201226164144.jpg)]

解决线程安全问题

售票出现的问题

  1. 卖票过程中,出现了重票,错票—>出现了线程的安全问题

  2. 原因:当某个线程操作车票过程中,尚未操作完成时,其他线程参与进来,也操作车票。

  3. 解决办法:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程A操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程A出现了阻塞,也不能被改变。

  4. 在Java中,通过同步机制,来解决线程安全问题。

  5. 同步的方式,解决了线程的安全问题。-----好处

    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。----局限性

方式一:同步代码块

synchronized(同步监视器){

//需要被同步的代码

}

说明:操作共享数据的代码,即为需要被同步的代码

共享数据:多个线程共同操作的变量。比如:ticket就是共享的数据。

同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。

要求:多个线程必须要共用同一把锁。

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

解决线程安全问题:

class Window implements Runnable{
    private int ticket = 100;

    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized(obj){
                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                    ticket --;
                }
                else{
                    break;
                }
            }
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        t1.setName("窗口1");
        Thread t2 = new Thread(window);
        t2.setName("窗口2");
        Thread t3 = new Thread(window);
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

关于同步方法的总结

1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

2、非静态的同步方法,同步方法是:this

静态的同步方法,同步监视器是:当前类本身。

方式三: ReentrantLock

/**
 * 解决线程安全问题的方式三:Lock锁-----JDK5.0新增
 */
class Windows implements  Runnable{
    private int ticket = 100;
    //1、实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);//公平锁
    @Override
    public void run() {
        while(true){
            try{
                //2、调用锁定方法lock()
                lock.lock();

                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket --;
                }else{
                    break;
                }
            }finally {
                //3、调用解锁方法unlock()
                lock.unlock();
            }

        }
    }
}
@SuppressWarnings("all")
public class LockTest {
    public static void main(String[] args) {
        Windows w = new Windows();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

线程安全的懒汉式:

方式一:效率稍差

public class BankTest{
    
}
class Bank{
    private Bank(){}
    private static  Bank bank = null;
    public static Bank getBank(){
        synchronized(Bank.class){
            if(bank == null){
            bank = new Bank();
            }
             return bank;
        }
       
    }
}

方式二:效率更高

if(bank == null){
    synchronized(Bank.class){
        if(bank == null){
            bank = new Bank();
        }
    }
}
return bank;

synchronized和lock有什么异同?

synchronizedlock(JDK5.0新增的)
相同二者都可以解决线程安全问题二者都可以解决线程安全问题
不同在执行完相应的同步代码以后,自动的释放同步监视器需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

优先使用顺序:

Lock——>同步代码块——>同步方法

死锁

演示线程的死锁问题

public class ThreadTest {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

说明:

1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

2)使用同步时,要避免出现死锁。

线程的通信

例:使用两个线程打印1-100.线程1,线程2交替打印

class Number implements Runnable{
    private int number = 1;


    @Override
    public void run() {

        while(true){
            synchronized (this){
                try{
                    notify();
                    if (number <= 100){
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName() + ":" + number);
                        number ++;
                        wait();//使得调用wait()的线程进入阻塞状态,并且会释放锁
                    }else{
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

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();
    }
}

涉及到的三个方法:

1、wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

2、notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

3、notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

说明:

1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

否则会出现IllegalMonitorStateException异常

3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的。

sleep()和wait()的异同?

sleep()wait()
相同点一旦执行方法,都可以使得当前的线程进入阻塞状态一旦执行方法,都可以使得当前的线程进入阻塞状态
不同点Thread类中声明sleep();sleep()可以在任何需要的场景下调用;sleep()不会释放锁Object类中声明wait();wait()必须使用在同步代码块或同步方法中;wait()会释放锁

经典的生产者消费者问题

//店员
class Clerk{

    private int productCount = 0;

    //消费产品
    public synchronized void customerProduct(){
        if (productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount --;

            notify();
        }else{
            System.out.println("柜台没有了....");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //生产产品
    public synchronized void produceProduct(){
        if (productCount < 20){
            productCount ++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
            notify();
        }else{
            System.out.println("柜台满了....");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
//生产者
class Producer extends Thread{

    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品.......");
        while(true){

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
//消费者
class Customer extends Thread{

    private Clerk clerk;

    public Customer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品.......");
        while(true){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.customerProduct();
        }
    }
}




public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Customer c1 = new Customer(clerk);
        c1.setName("消费者1");
        Customer c2 = new Customer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();
    }

}

多线程的创建,方式三:实现Callable接口

JDK5.0新增

步骤:

1、创建一个实现Callable的实现类

2、实现call()方法,将此线程需要执行的操作声明在call()中

3、创建Callable接口实现类的对象

4、将此对象传递到FutureTask构造器中,创建FutureTask的对象

5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start()调用。

6、获取Callable中call方法的返回值。

class NumThread implements Callable{
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {

    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask futureTask = new FutureTask(numThread);
        new Thread(futureTask).start();

        try {
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

如何理解实现Callable接口的方式比实现Runnable接口创建多线程方式更强大?

1、它可以有返回值

2、方法可以抛出异常,被外面的操作捕获,获取异常的信息

3、支持泛型的返回值

4、需要借助FutureTask类,比如获取返回结果

多线程的创建,方式四:使用线程池

JDK5.0新增

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。

好处:

1、提高响应速度

2、降低资源消耗

3、便于线程管理

步骤:

1、提供指定线程数量的线程池

2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class NumberThread1 implements Callable{

    @Override
    public Object call() throws Exception {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + "实现callable接口" + i);
            }
        }
        return null;
    }
}

public class ThreadPool {
    public static void main(String[] args) {


        ExecutorService service = Executors.newFixedThreadPool(10);
        //设置线程池的属性
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        service1.setCorePoolSize(15);
        //service1.setKeepAliveTime();
        service.execute(new NumberThread());//适合使用于Runnable
        Future submit = service.submit(new NumberThread1());//适合适用于Callable
        try {
            Object o = submit.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Java常用类

字符串相关的类

String类

String字符串:使用一对""引起来表示。
1、String声明为final的,不可被继承
2、String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3、String内部定义了final char[] value用于存储字符串数据
4、String:==代表一个不可变的字符序列。==简称:不可变性
体现:1、当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值

​ 2、当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value值进行赋 值

​ 3、当调用String的replace()修改字符或字符串时,也必须重新指定内存区域赋值,不能使用原有的value值进行赋值

5、通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。

6、字符串常量池中是不会存储相同内容的字符串的。

JDK1.6在方法区;JDK1.7在堆空间;JDK1.8在方法区。

方式一:通过字面量定义的方式
 @Test
    public void test1(){
        String s1 = "abc";//字面量的定义方式
        String s2 = "abc";
        //s1 = "hello";

        System.out.println(s1 == s2);//true

        System.out.println(s1);//abc
        System.out.println(s2);//abc

        System.out.println("********************");

        String s3 = "abc";
        s3 += "def";
        System.out.println(s3);//abcdef
        System.out.println("********************");
        String s4 = "abc";
        String s5 = s4.replace('a','m');
        System.out.println(s4);//abc
        System.out.println(s5);//mbc
    }
方式二:通过new+构造器的方式
@Test
    public void test2(){
        //此时的s1和s2的数据都是声明在方法区的常量池中
        String s1 = "javaEE";
        String s2 = "javaEE";

        //s3和s4声明在堆空间中
        String s3 = new String("javaEE");
        String s4 = new String("javaEE");

        System.out.println(s1 == s2);//true
        System.out.println(s1 == s4);//false
        System.out.println(s3 == s4);//false

        Person p1 = new Person("Tom", 12);
        Person p2 = new Person("Tom", 12);

        System.out.println(p1.name.equals(p2.name));//true
        System.out.println(p1.name == p2.name);//true

    }

使用String s = new String(“abc”)的方式创建对象,在内存中创建了几个对象?

两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”

结论:1、常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

2、只要其中有一个是变量,结果就在堆中。

3、如果拼接的结果调用intern()方法,返回值就在常量池中

@Test
    public void test3(){
        String s1 = "javaEE";
        String s2 = "hadoop";
        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "hadoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;
        String s8 = s5.intern();


        System.out.println(s3 == s4);//true

        System.out.println(s3 == s5);//false

        System.out.println(s3 == s6);//false

        System.out.println(s3 == s7);//false

        System.out.println(s5 == s6);//false

        System.out.println(s5 == s7);//false

        System.out.println(s6 == s7);//false

        System.out.println(s3 == s8);//true
    }

面试题

public class Test {
    String str = "good";
    char[] ch = {'t','e','s','t'};

    public void change(String str,char ch[]){
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.change(test.str,test.ch);
        System.out.println(test.str);//good
        System.out.println(test.ch);//best
    }
}
常用方法

int length():返回字符串长度
char charAt(int index):返回某索引处的字符
boolean isEmpty():判断是否是空字符串
String toLowerCase():使用默认语言环境,将所有字符转换为小写
String toUpperCase():使用默认语言环境,将所有字符转换为大写
String trim():返回字符串副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等价于用"+"
int compareTo(String anotherString):比较两个字符串大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的beginIndex开始截取
String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex截取到endIndex

@Test
    public void test1(){
        String s1 = "helloWorld";
        System.out.println(s1.length());//10
        System.out.println(s1.charAt(0));//h
        System.out.println(s1.charAt(9));//d
        //System.out.println(s1.charAt(10));//java.lang.StringIndexOutOfBoundsException
        System.out.println(s1.isEmpty());//false
        System.out.println(s1.toLowerCase());//helloworld
        System.out.println(s1.toUpperCase());//HELLOWORLD
        String s3 = " he l l o ";
        System.out.println(s3.trim());//he l l o
        System.out.println(s1.equals(s1.toUpperCase()));//false
        System.out.println(s1.equalsIgnoreCase(s1.toUpperCase()));//true
        System.out.println(s1.concat("123"));//helloWorld123
        System.out.println(s1.compareTo(s1.concat("1")));//涉及到字符串排序;-1
        System.out.println(s1.concat("13123").substring(11));//3123
        System.out.println(s1.concat("2312321321").substring(12,18));//123213

    }
类型转换
String类与其他结构之间的转换
String与基本数据类型、包装类之间的转换
String---->基本数据类型、包装类:调用包装类的静态方法Integer.parseInt()
基本数据类型、包装类---->String:调用String重载的valueOf(xxx)

String与char[]之间的转换

String------>char[]:调用String的toCharArray()
char[]------>String:调用String的构造器

String与byte[]之间的转换

String--->byte[]:调用String的getBytes()
byte--->String:调用String的构造器
编码:字符串 --->字节
解码:编码的逆过程,字节--->字符串
 
说明:解码时,要求解码使用的字符集必须要与编码时使用的字符集一致,否则会出现乱码。
@Test
    public void test3() throws UnsupportedEncodingException {
        String str1 = "abc123中国";
        byte[] bytes = str1.getBytes();
        System.out.println(Arrays.toString(bytes));

        byte[] bytes1 = str1.getBytes("gbk");//使用gbk字符集进行编码
        System.out.println(Arrays.toString(bytes1));

        String str2 = new String(bytes);//使用默认的字符串,进行解码
        System.out.println(str2);

        String str3 = new String(bytes1,"gbk");
        System.out.println(str3);
        
    }

StringBuffer和StringBuilder的使用

StringStringBufferStringBuilder
相同点底层使用char[]存储底层使用char[]存储底层使用char[]存储
不同点不可变的字符序列可变的字符序列,线程安全,效率低可变的字符序列,线程不安全,效率高;JDK5.0新增

源码分析:

String str = new String();//new char[0];

String str1 = new String(“abc”);//new char[]{‘a’,‘b’,‘c’};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。

sb1.append(‘a’);//value[0] = ‘a’;

sb1.append(‘b’);//value[1]=‘b’;

StringBuffer sb2 = new StringBuffer(“abc”);//char[] value = new char[“abc”.length() + 16]

JDK8之前日期时间API

System类中的currentTimeMillis(),返回时间戳

@Test
    public void test1(){
        long time = System.currentTimeMillis();
        System.out.println(time);
    }

Date类

java.util.Date类
两个构造器使用

构造器一:Date():创建一个对应当前时间的Date对象

构造器二:创建指定毫秒数的Date对象

两个方法的使用

toString():显示当前的年,月,日,时,分,秒

getTime():获取当前Date对象对应的毫秒数。(时间戳)

java.sql.Date类对应着数据库中的日期类型的变量

@Test
    public void test2(){
        //构造器一:Date():创建一个对应当前时间的Date对象
        Date date1 = new Date();
        System.out.println(date1.toString());
        System.out.println(date1.getTime());

        //构造器二:创建指定毫秒数的Date对象
        Date date2 = new Date(1609141206869L);
        System.out.println(date2.toString());

        //创建java.sql.Date对象
        java.sql.Date date3 = new java.sql.Date(1609141206869L);
        System.out.println(date3.toString());
    }

java.text.SimpleDateFormat

SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1、两个操作
格式化:日期---->字符串
解析:格式化的逆过程---->日期
2、SimpleDateFormat的实例化

 @Test
    public void testSimpleDateFormat() throws ParseException {
        //实例化SimpleDateFormat
        SimpleDateFormat sdf = new SimpleDateFormat();
        //格式化日期
        Date date = new Date();
        String format = sdf.format(date);
        System.out.println(format);

        //解析 格式化逆过程:字符串---->日期
        String str = "2017-01-01 上午11:43";//不方便
        Date date1 = sdf.parse(str);
        System.out.println(date1);

        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format1 = sdf1.format(date);
        System.out.println(format1);

    }

练习题:将字符串"2020-09-08"转换为java.sql.Date

@Test
public void test1(){
    String birth = "2020-09-08";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse(birth);
    java.sql.Date birthDate = new java.sql.Date(date.getTime());
    System.out.println(birthDate);
}

java.util.Calendar

两种实例化方式:1、实例化它的子类GregorianCalendar

​ 2、调用这个类的getInstance()静态方法

JDK8中新日期API

LocalDate、LocalTime、LocalDateTime的使用

@Test
    public void test1(){
        //now():获取当前日期,时间,日期+时间
        LocalDate date = LocalDate.now();
        LocalTime time = LocalTime.now();
        LocalDateTime dateTime = LocalDateTime.now();

        System.out.println(date);
        System.out.println(time);
        System.out.println(dateTime);

        //of():设置指定的年月日,时分秒
        LocalDateTime localDateTime = LocalDateTime.of(2020, 12, 28, 16, 51, 20);
        System.out.println(localDateTime);

        //getXxx()
        System.out.println(localDateTime.getDayOfMonth());
        System.out.println(localDateTime.getDayOfWeek());
        System.out.println(localDateTime.getDayOfYear());
    }

后续请参考-------->尚硅谷Java基础484-486

Java比较器

一、java中对象,正常情况下,只能进行比较:== 或!=。不能使用>或<的
但是在开发场景中,我们需要对多个对象进行排序。需要比较对象的大小
使用两个接口中任何一个:Comparable或Comparator
二、Comparable接口的使用(自然排序

Comparable接口的使用举例
1、像String,包装类等实现了Comparable接口,重写了compareTo()方法,给出了比较两个对象大小的方式
2、像String,包装类重写compareTo()方法以后,进行了从小到大的排列
3、重写compareTo()的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。

4、对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写CompareTo()方法
在CompareTo(obj)方法中指明如何排序

String类

 @Test
    public void test1(){
        String[] arr = new String[]{"AA","CC","MM","GG","KK","JJ","DD"};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }

当需要进行比较的类是自定义类时,让自定义类实现Comparable接口,重写CompareTo()方法

public class Goods implements Comparable{
    private String name;
    private double price;

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public Goods() {
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

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

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

    //指明商品比较大小的方式:按照价格从低到高排序
    @Override
    public int compareTo(Object o) {
        //方式一:
        if (o instanceof Goods){
            Goods goods = (Goods)o;
            if (this.price > goods.price){
                return 1;
            }else if (this.price < goods.price){
                return -1;
            }else{
                 //return 0;
                return this.name.compareTo(goods.name);//价格一样时,按名称比较
            }
            //方式二:
            //return Double.compare(this.price,goods.price);
        }
        throw new RuntimeException("传入的数据类型不一致!");
    }
}

三、java.util.Comparator(定制排序

1、Comparator接口(定制排序):当元素的类型没有实现Comparable接口而又不方便修改代码
或者排序规则不适合当前的操作,就可以考虑使用Comparator的对象来排序。
2、重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2
如果返回0,表示相等;
返回负整数,表示o1小于o2.

String类

@Test
    public void test3(){
        String[] arr = new String[]{"AA","CC","MM","GG","KK","JJ","DD"};
        Arrays.sort(arr, new Comparator() {
            //按照字符串从大到小的顺序排序
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof String && o2 instanceof String){
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    return -s1.compareTo(s2);
                }
                throw new RuntimeException("输入的数据类型不一致");
            }
        });
        System.out.println(Arrays.toString(arr));
    }

自定义类

@Test
    public void test2(){
       Goods[] arr = new Goods[5];
       arr[0] = new Goods("lenovo",34);
       arr[1] = new Goods("dell",43);
       arr[2] = new Goods("xiaomi",12);
       arr[3] = new Goods("huaWei",65);
       arr[4] = new Goods("lenovo",21);
       Arrays.sort(arr, new Comparator() {
           @Override
           public int compare(Object o1, Object o2) {
               if (o1 instanceof Goods && o2 instanceof Goods){
                   Goods g1 = (Goods) o1;
                   Goods g2 = (Goods) o2;
                   if (g1.getName().equals(g2.getName())){
                       return -Double.compare(g1.getPrice(),g2.getPrice());
                   }else{
                       return g1.getName().compareTo(g2.getName());
                   }
               }
               throw new RuntimeException("输入的数据类型不一致");
           }
       });
       System.out.println(Arrays.toString(arr));
    }

Comparable和Comparator的使用对比

Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小

Comparator接口属于临时性的比较。

System类

三个属性:in,out,err

方法:

void exit(int status);//0表示正常退出,非0表示异常退出。

void gc();//请求系统进行垃圾回收,至于是否回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况

String getProperty(String key);//获得系统中属性名为key的属性对应的值。

Math类

abs 绝对值

acos,asin,atan,cos,sin,tan 三角函数

sqrt平方根

pow(double a,double b)a的b次幂

log自然对数

exp e为底指数

max(double a,double b)

min(double a,double b)

random()返回0.0到1.0的随机数

long round(double a)double转换为long

toDegrees(double angrad)弧度---->角度

toRadians(double angdeg)角度---->弧度

BigInteger和BigDecimal

BigInteger可以表示不可变的任意精度的整数

BigInteger可以表示不可变的任意精度的浮点型

枚举类和注解

枚举类

一、枚举类的使用

1、枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类

2、当需要定义一组常量时,强烈建议使用枚举类

3、如果枚举类中只有一个对象,则可以作为单例模式的实现方式

二、如何定义枚举类

方式一:JDK5.0之前,自定义枚举类

方式二:JDK5.0时,可以使用enum关键字定义枚举类

自定义枚举类

public class SeasonTest {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
    }
}
//自定义枚举类
class Season{
    //1、声明Season对象的属性:private final修饰
    private final String seasonName;
    private final String seasonDesc;
    //2、私有化类的构造器,并给对象属性赋值
    private Season(String seasonName,String seasonDesc){
        this.seasonDesc = seasonDesc;
        this.seasonName = seasonName;
    }
    //3、提供当前枚举类的多个对象:public static final的
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","夏日炎炎");
    public static final Season AUTUMN = new Season("秋天","秋高气爽");
    public static final Season WINTER = new Season("冬天","冰天雪地");

    //4、其他:获取枚举类对象的属性

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}

enum枚举类

enum Season{
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");
    
    private final String seasonName;
    private final String seasonDesc;
    
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}

三、Enum类中的常用方法

values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。

valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象。

toString():返回当前枚举类对象常量的名称

注解

如何自定义注解?

①注解声明为:@interface

②内部定义成员,通常使用value表示

③可以指定成员的默认值,使用default定义

④如果自定义注解没有成员,表明是一个标识作用

Java集合

一、集合框架的概述
1、集合,数组都是对多个数据进行存储操作的结构,简化JAVA容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
2.1、数组在存储多个数据方面的特点:
①一旦初始化以后,其长度就确定了。
②数组一旦定义好,其元素的类型也就确定好了。也就只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
2.2、数组在存储多个数据方面的缺点:
①一旦初始化以后,其长度就不可修改。
②数组中提供的方法非常有限,对于添加,删除,插入数据等操作,非常不便,同时效率也不高。
③获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
④数组存储数据的特点:有序,可重复。对于无序、不可重复的需求,不能满足。
二、集合框架
|Collection接口:单列集合,用来存储一个一个的对象*******|
|********List接口:存储有序的,可重复的数据。"动态"数组|
|**********ArrayList,LinkedList,Vector|
|***************Set接口:存储无序的,不可重复的数据。***************|
|**HashSet,LinkedHashSet,TreeSet|
|Map接口:双列集合,用来存储一对(key-value)一对的数据
|
|HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
|
三、Collection接口中的方法的使用

Collection接口

Collection接口中声明的方法的测试
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().

@Test
    public void test1(){
        Collection coll = new ArrayList();
        //add(Object e):将元素e添加到集合coll中
        coll.add("AA");
        coll.add("BB");
        coll.add(123);//自动装箱
        coll.add(new Date());

        //size():获取添加元素的个数
        System.out.println(coll.size());

        //addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("CC");
        coll.addAll(coll1);
        System.out.println("添加之后:" + coll.size());//6

        //clear():清空集合元素
        coll.clear();

        //isEmpty():判断集合是否为空
        System.out.println(coll.isEmpty());//true
    }
@Test
    public void test1(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(new String("Tom"));
        coll.add(false);
        coll.add(456);
        coll.add(new Person("邱周童", 19));


        //contains(Object obj):判断当前集合中是否包含obj
        System.out.println(coll.contains(123));
        System.out.println(coll.contains(new String("Tom")));
        System.out.println(coll.contains(new Person("邱周童", 19)));//false

        //containsAll():判断当前集合是否包含多个元素

        //remove(Object obj):移除元素
        coll.remove(123);
        System.out.println(coll);

        //removeAll(Collection coll1):从当前集合中移除coll1中所有元素

        //retainAll(Collection coll):求交集

        //equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同

        //hashcode():返回当前对象的哈希值
        System.out.println(coll.hashCode());

        //集合----->数组:toArray()
        Object[] arr = coll.toArray();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //数组----->集合:调用Arrays类的静态方法asList()
        List<String> list = Arrays.asList(new String[]{"aa", "bb", "cc"});
        System.out.println(list);
        
        //iterator():返回Iterator接口的实例,用于遍历集合元素
    }

iterator()方法

集合元素的遍历操作,使用迭代器Iterator接口
1、内部的方法:hasNext():判断是否还有下一个元素和next():指针下移。将下移元素返回
2、集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
3、内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove().

@Test
publiv void test(){
   Collection coll = new ArrayList();
    coll.add(123);
    coll.add(new String("Tom"));
    coll.add(new Person("邱周童",19));
    coll.add(456);
    coll.add(false);
    //遍历元素
    Iterator iterator = coll.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
    //删除元素
    iterator = coll.iterator();
    while(iterator.hasNext()){
        Object obj = coll.next();
        if("Tom".equals(obj)){
            iterator.remove();
        }
    }
    
    //重新遍历元素,因为已经遍历过了一次,iterator的指针已经指到下面了,所以需要重新获取iterator
    iterator = coll.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }  
}

JDK5.0新增了foreach循环,用于遍历集合,数组

@Test
public void test(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(new String("Tom"));
    coll.add(new Person("邱周童",19));
    coll.add(456);
    coll.add(false);
    for(Object obj:coll){
        System.out.println(o);
    }
}

List接口

ArrayList,LinkedList,Vector三者的异同?

相同点:三个类都实现了List接口,存储数据的特点相同:存储有序的,可以重复的数据

不同点:

ArrayList(主要实现类,JDK1.2):线程不安全,效率高,底层使用Object[] elementData存储

LinkedList:底层使用双向链表存储,对于频繁的插入和删除操作使用它效率比ArrayList高

Vector(古老实现类,JDK1.0):线程安全,效率低,底层使用Object[] elementData存储

Arraylist的源码分析:JDK7

ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData

list.add(123);//elementData[0] = new Integer(123);

list.add(11);//如果此次的添加导致elementData数组容量不够,则扩容。

默认情况下。扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

Arraylist的源码分析:JDK8

ArrayList list = new ArrayList();//底层Object[] elementData初始化为{},并没有创建初始化长度为10的数组

list.add();//第一次调用add()方法时,底层才创建了长度10的数组,并将数据123添加到elementData[0]

后续的添加和扩容操作与jdk7无异

小结:jdk7中的ArrayList的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式

LinkedList的源码分析

Linkedlist list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null

list.add(123);//将123封装到Node中,创建了Node对象。

其中:Node定义为:

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;
    }
}
Vector的源码分析

JDK7和JDK8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。

在扩容方面,默认扩容为原来的数组长度的2倍。

List接口中的常用方法
void add(int index,Object ele):在index位置插入ele元素
boolean addAll(int index,Collection eles):从index位置开始将eles中所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在当前集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index,Object ele):设置指定index位置的元素为ele
List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合
    
    
public class ListTest {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");
        list.add(new Person("Tom",12));
        System.out.println(list);

        //void add(int index,Object ele)
        list.add(1,"BB");
        System.out.println(list);

        //boolean addAll(int index,Collection eles)
        List<Integer> list1 = Arrays.asList(1, 2, 3);
        list.addAll(list1);
        System.out.println(list.size());

        //Object get()
        System.out.println(list.get(7));

        //indexOf():如果不存在返回-1
        System.out.println(list.indexOf(456));

        //lastIndexOf()
        System.out.println(list.lastIndexOf(123));

        //Object remove(int index):是Collection接口方法Object remove(Object obj)的重载方法
        list.remove(0);
        System.out.println("删除后" + list);

        //Object set()
        list.set(1,"AA");
        System.out.println(list);

        //List subList(int fromIndex,int toIndex):返回从fromIndex位置的左闭右开的子集合
        System.out.println(list.subList(2, 4));
    }
}

总结:常用方法

增:add(Object obj)

删:remove(int index) / remove(Object obj)

改:set(int index,Object ele)

查:get(int index)

插:add(int index,Object ele)

长度:size()

遍历:增强for循环,迭代器,普通的循环

@Test
public void test(){
    ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
    //方式一:Iterator迭代器
    Iterator it = list.iterator
    while(it.hasNext()){
        System.out.println(it.next());
    }
    //方式二:增强for循环
    for(Object obj:list){
        System.out.println(obj);
    }
    //方式三:普通for循环
    for(int i = 0;i < list.size();i ++){
        System.out.println(list.get(i));
    }
}
面试题
@Test
    public void test(){
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//1 2
    }

    private static void updateList(List list) {
        list.remove(2);//按照索引删
        list.remove(new Integer(2));//使用Collection接口中的remove方法返回结果为1 3
    }

Set接口

存储无序的,不可重复的数据

HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值

​ LinkedHashSet:HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历

TreeSet:可以按照添加对象指定属性,进行排序。

Set接口中没有额外定义新的方法,使用的都是Collection中声明的方法。

要求:

向Set中添加的数据,其所在的类一定要重写hashcode()和equals()

重写的hashcode()和equals()尽可能保持一致性。相等的对象必须具有相等的散列码。

重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashcode

HashSet添加元素过程

Set:存储无序的,不可重复的数据

以HashSet为例说明:

1、无序性:不等于随机性,存储的数据在底层中并非按照数组索引的顺序添加,而是根据数据的哈希值

2、不可重复性:保证添加的元素按照equals()判断时,不能返回true;即:相同的元素只能添加一个。

添加元素的过程:以HashSet为例:

我们向HashSet中添加元素a,首先调用元素a所在类的hashcode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中存放位置(即为:索引位置),判断

如果此位置上没有其他元素,则元素a添加成功。--------->情况1

如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值;如果hash值不相同,则元素a添加成功。--------->情况2

如果hash值相同,进而需要调用元素a所在类的equals()方法;

equals()返回true,元素a添加失败

equals()返回false,则元素a添加成功。-------->情况2

对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。

jdk7:元素a放到数组中,指向原来的元素。

jdk8:原来的元素在数组中,指向元素a

总结:七上八下

LinkedHashSet

LinkedHashSet是作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。

对于频繁的遍历操作,LinkedHashSet效率高于HashSet。

TreeSet

1、向TreeSet中添加的数据,要求是相同类的对象。

2、两种排序方式:自然排序(Comparable)和定制排序(Comparator)

set.add(34);
set.add(12);
set.add(43);
set.add(-10);

添加自定义属性(自然排序)

package com.cs.java;

import java.util.Objects;

public class User implements Comparable {
    String name;
    int age;

    public User(){

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

    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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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

    //按照姓名从小到大排序,年龄从大到小排列
    @Override
    public int compareTo(Object o) {
        if( o instanceof User){
            User user = (User)o;
          //  return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if (compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入类型不匹配!");
        }
    }
}


 @Test
    public void test2(){
        TreeSet treeSet = new TreeSet();

        treeSet.add(new User("Tom",12));
        treeSet.add(new User("Jack",12));
        treeSet.add(new User("Jim",12));
        treeSet.add(new User("Mike",12));
        treeSet.add(new User("Jerry",12));

        Iterator it = treeSet.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

    }

定制排序

 @Test
    public void test3(){
        Comparator com = new Comparator() {
            //按照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User){
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("输入数据类型不匹配");
                }
            }
        };
        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jack",123));
        set.add(new User("Jim",1));
        set.add(new User("Mike",10));
        set.add(new User("Jerry",15));

        Iterator it = set.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }

Map接口

双列数据,存储key-value对的数据 -----类似于高中的函数:y = f(x)

HashMap

HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value

底层:数组+链表(jdk7及之前)

​ 数组+链表+红黑树(jdk8)

Map结构的理解:

Map中的key:无序的、不可重复的、使用Set存储所有的key —>key所在的类要重写equals()和hashcode()

Map中的value:无序的、不可重复的、使用Collection存储所有的value—>value所在的类要重写equals()

一个键值对:key-value构成了一个Entry对象。

Map中的entry:无序的、不可重复的,使用Set存储所有的entry

HashMap底层实现原理(以jdk7为例)

HashMap map = new HashMap();

在实例化以后,底层创建了一个长度是16的一维数组Entry[] table.

…可能执行过多次put…

map.put(key1,value1):首先,调用key1所在类的hashcode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。-------情况1

如果此位置上的数据不为空(意味着此位置上存放一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

​ 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。-------情况2

​ 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals()方法,比较:如果equals()返回false:此时key1-value1添加成功。-------情况3

​ 如果equals()返回true:使用value1替换相同key的value值。

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断的添加过程中,会涉及到扩容问题:扩容为原来的2倍,并将原有的数据复制过来。

jdk8相较于jdk7在底层实现方面的不同:

1、new HashMap():底层没有创建一个长度为16的Entry数组

2、jdk8底层的数组是:Node[],而非Entry[]

3、首次调用put()方法时,底层创建长度为16的数组

4、jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。

DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16

DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75

threshold:扩容的临界值,=容量*填充因子: 16✖0.75 =>12

TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8

MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

LinkedHashMap

HashMap的子类

​ LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序来实现遍历。

​ 原因:在原有的HashMap底层结构基础上,添加上了一对指针,指向前一个和后一个元素。

​ 对于频繁的遍历操作,此类执行效率高于HashMap。

底层实现原理:

static class Entry<K,V> extends HashMap,Node<K,V>{
    Entry<K,V> before,after;//能够记录添加的元素的先后顺序
    Entry(int hash,K key,V value,Node<K,V> next){
        super(hash,key,value,next);
    }
}

TreeMap

TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序

​ 底层使用红黑树

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象

因为要按照key进行排序:自然排序、定制排序

Hashtable

Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value

Properties

常用来处理配置文件。key和value都是String类型

面试题

1、HashMap的底层实现原理?

2、HashMap和Hashtable的异同?

3、CurrentHashMap与Hashtable的区别?

Map接口中常用方法

增:Object put(Object key,Object value)

删除:Object remove(Object key)

清空:void clear()

Collections工具类

Collections:操作Collection、Map的工具类

reverse():反转list中的元素

shuffle():对list集合元素进行随机排序

sort():根据元素的自然顺序对指定List集合元素按升序排序

sort():根据指定的Comparator产生的顺序对list集合元素进行排序

swap():将指定list集合中的i处元素和j处元素进行交换

max():返回最大元素

min():返回最小元素

frequency():返回指定集合中指定元素的出现次数

copy():将src中的内容复制到dest中

replaceAll():使用新值替换

synchronizedxxx():返回线程安全的xxx

面试题:Collection和Collections的区别?

泛型和File

泛型的使用

jdk5新增的特性

/**
 * 泛型的使用
 */
public class GenericTest {
    @Test
    public void test(){
        ArrayList list = new ArrayList();
        list.add(78);
        list.add(79);
        list.add(80);
        list.add(81);
        //问题一:类型不安全
        //list.add("Tom");

        for (Object o : list) {
            //问题二:ClassCastException
            int i = (Integer) o;
            System.out.println(i);
        }
    }
    @Test
    //在集合中使用泛型的情况
    public void test1(){
        ArrayList<Integer> list = new ArrayList<>();
        list.add(88);
        list.add(89);
        list.add(90);
        list.add(91);

        //方式一
        for (Integer score : list) {
            int i = score;
            System.out.println(i);
        }
        //方式二
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

File类的使用

File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)

File类声明在java.io包下

File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。

后续File类的对象常会作为参数传递给流的构造器中,指明读取或写入的"终点".

1、如何创建File类的实例?
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)

2、相对路径:相较于某个路径下,指明的路径
绝对路径:包含盘符在内的文件或文件目录的路径
3、路径分隔符
windows:\
unix:/

@Test
    public void test() {
        //构造器1
        File file1 = new File("hello.txt");
        File file2 = new File("E:\\java\\Java、base\\JavaSenlor\\day08");

        //构造器2
        File file3 = new File("E:\\java\\Java、base\\JavaSenlor\\day08", "JavaSenior");

        //构造器3
        File file4 = new File(file3, "hi.txt");

    }

常用方法

 /**
     * 常用方法
     * getAbsolutePath():获取绝对路径
     * getPath():获取路径
     * getName():获取名称
     * getParent():获取上层文件目录路径。若无,返回null
     * length():获取文件长度
     * lastModified():获取最后一次的修改时间,毫秒值
     * <p>
     * 如下两个方法适用于文件目录:
     * String[] list():获取指定目录下的所有文件或者文件目录的名称数组
     * File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组
     */
    @Test
    public void test1() {
        File file1 = new File("hello.txt");
        File file2 = new File("E:\\java\\Java、base\\JavaSenlor\\day08");

        System.out.println(file1.getAbsolutePath());
        System.out.println(file1.getPath());
        System.out.println(file1.getName());
        System.out.println(file1.getParent());
        System.out.println(file1.length());
        System.out.println(file1.lastModified());

        System.out.println();

        System.out.println(file2.getAbsolutePath());
        System.out.println(file2.getPath());
        System.out.println(file2.getName());
        System.out.println(file2.getParent());
        System.out.println(file2.length());
        System.out.println(file2.lastModified());

    }

    @Test
    public void test2() {
        File file = new File("E:\\java\\Java、base\\JavaSenlor");

        String[] list = file.list();
        for (String s : list) {
            System.out.println(s);
        }

        File[] files = file.listFiles();

        for (File file1 : files) {
            System.out.println(file1);
        }
    }

    /**
     * boolean renameTo(File dest):把文件重命名为指定的文件路径
     * 比如:file1.renameTo(file2)为例:
     * 要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
     */
    @Test
    public void test3() {
        File file = new File("hello.txt");
        File file1 = new File("E:\\java\\Java、base\\JavaSenlor");

        boolean b = file.renameTo(file1);
        System.out.println(b);
    }

    /**
     * 判断功能
     * isDirectory():判断是否是文件目录
     * isFile():判断是否是文件
     * exists():判读是否存在
     * canRead():判断是否可读
     * canWrite():判断是否可写
     * isHidden():判断是否隐藏
     */
    @Test
    public void test5() {
        File file1 = new File("hello.txt");

        System.out.println(file1.isDirectory());
        System.out.println(file1.isFile());
        System.out.println(file1.exists());
        System.out.println(file1.canRead());
        System.out.println(file1.canWrite());
        System.out.println(file1.isHidden());
    }

    /**
     * 创建硬盘中对应的文件或文件目录
     * createNewFile():创建文件,若文件存在,则不创建,返回false
     * mkdir():创建文件目录。如果此文件目录不存在,就不创建了。如果此文件目录的上层
     * mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
     * <p>
     * 删除磁盘文件或文件目录
     * public boolean delete():删除文件或者文件夹
     * 删除注意事项:
     * Java中的删除不走回收站。
     */
    @Test
    public void test6() throws IOException {
        File file = new File("hello.txt");
        if (!file.exists()) {
            file.createNewFile();
            System.out.println("创建成功!");
        }else{
            file.delete();
            System.out.println("删除成功!");
        }
    }
@Test
    public void test7(){
        //文件目录的创建
        File file = new File("E:\\java\\Java、base\\JavaSenlor\\小邱邱");
        boolean mkdir = file.mkdir();
        if (mkdir){
            System.out.println("创建成功!");
        }

    }

IO流

一、流的分类

按操作的数据单位不同分为:字节流,字符流

按数据流的流向不同分为:输入流,输出流

按流的角色的不同分为:节点流,处理流

【抽象基类】字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

二、流的体系结构

抽象基类节点流(或文件流)缓冲流(处理流的一种)
InputStreamFileInputStreamBufferedInputStream
OutputStreamFileOutputStreamBufferedOutputStream
ReaderFileReaderBufferedReader
WriterFileWriterBufferedWriter

字符流

文件的读入

public class FileReaderWriterTest {
    /**
     * 说明点:
     * 1、read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
     * 2、异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
     */
    @Test
    public void testFileReader(){

        FileReader fr = null;
        try {
            //1、实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");
            //2、提供具体的流
            fr = new FileReader(file);

            //3、数据的读入
            //read():返回读入的一个字符。如果达到文件末尾,返回-1

            //方式一
       /* int data = fr.read();
        while(data != -1){
            System.out.print((char) data);
            data = fr.read();
        }*/
            //方式二
            int data;
            while ((data = fr.read()) != -1){
                System.out.println((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //4、流的关闭操作
        try {
            if (fr != null)
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //对read()操作升级:使用read的重载方法
    @Test
    public void testFileReader1(){
        FileReader fileReader = null;
        try {
            //1、File类的实例化
            File file = new File("hello.txt");
            //2、FileReader流的实例化
            fileReader = new FileReader(file);
            //3、读入的操作
            //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾返回-1
            char[] cbuf = new char[4];
            int len;
            while((len = fileReader.read(cbuf)) != -1){
                //方式一
                //错误的写法
                /*for (int i = 0; i < cbuf.length; i++) {
                    System.out.print(cbuf[i]);
                }*/
               /* for (int i = 0; i < len; i++) {
                    System.out.print(cbuf[i]);
                }*/
                //方式二
                String str = new String(cbuf, 0, len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileReader != null){
                //4、资源的关闭
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

文件的写出

/**
     * 从内存中写出数据到硬盘的文件里
     * 说明
     * 1、输出操作,对应的File可以不存在的。并不会报异常
     * 2、
     *    File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
     *    File对应的硬盘中的文件如果存在:
     *       如果流使用的构造器是:FileWriter(file,false)/FileWriter(file):对原有文件的覆盖
     *       如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容。
     */
    @Test
    public void testFileWriter() throws IOException {
        //1、提供File类的对象,指明写出到的文件
        File file = new File("hello.txt");

        //2、提供FileWriter的对象,用于数据的写出
        FileWriter fw = new FileWriter(file,true);

        //3、写出的操作
        fw.write("I have a dream!\n");
        fw.write("you need to have a dream!");

        //4、流资源的关闭
        fw.close();
    }

实现文件的复制

 @Test
    public void testFileReaderFileWriter(){
        FileReader fr = null;
        FileWriter fw = null;
        try {
            //1、创建File类的对象,指明读入和写出的文件
            File srcFile = new File("hello.txt");
            File destFile = new File("hello2.txt");

            //2、创建输入流和输出流的对象
            fr = new FileReader(srcFile);
            fw = new FileWriter(destFile);

            //3、数据的读入和写出操作
            char[] cbuf = new char[5];
            int len;//j记录每次读入到cbuf数组中的字符的个数
            while((len = fr.read(cbuf)) != -1){
                //每次写出len个字符
                fw.write(cbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            //4、关闭流资源
            try {
                if (fw != null) {
                    fw.close();
                }
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

不能使用字符流处理图片

字节流

/**
 * 测试FileInputStream和FileOutputStream的使用
 *
 * 结论:
 * 1、对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
 * 2、对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
 *
 * @author ASUS
 * date: 2021/1/7 15:54
 * @since JDK 1.8
 */    
@Test
    public void testFileInputStream(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File("face.jpg");
            File destFile = new File("face1.jpg");
            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //复制的过程
            byte[] buffer = new byte[5];
            int len;
            while ((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                if (fos != null){

                    fos.close();
                }
                if (fis != null){

                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    //指定路径下文件的复制
    public void copyFile(String srcPath,String destPath){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //复制的过程
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                if (fos != null){

                    fos.close();
                }
                if (fis != null){

                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    @Test
    public void testCopyFile(){
        long start = System.currentTimeMillis();
        String srcPath = "";
        String destPath = "";
        copyFile(srcPath,destPath);
        long end = System.currentTimeMillis();
        System.out.println("复制操作花费的时间为:" + (end - start));
    }

缓冲流(处理流的一种)

作用:提供流的读取、写入的速度

提高读写速度的原因:内部提供了一个缓冲区

处理流:就是"套接"在已有的流的基础上。

public class BufferedTest {
        /**
         * 实现非文本文件的复制
         */
        @Test
        public void BufferedStreamTest() {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
            try {
                //1、造文件
                File srcFile = new File("face.jpg");
                File destFile = new File("face1.jpg");
                //2、造流
                //2.1 造节点流
                fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);

                //2.2 造缓冲流
                bis = new BufferedInputStream(fis);
                bos = new BufferedOutputStream(fos);

                //3、复制的细节:读取,写入
                byte[] buffer = new byte[10];
                int len;
                while ((len = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //4、资源关闭
                //要求:先关闭外层的流,再关闭内层的流
                try {
                    if (bis != null) {

                        bis.close();
                    }
                    if (bos != null) {

                        bos.close();
                    }
                    if (fis != null) {

                        fis.close();
                    }
                    if (fos != null) {

                        fos.close();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
}

转换流(处理流的一种)

/**
 * 处理流之二:转换流的使用
 * 1、转换流:属于字符流
 *    InputStreamReader:将一个字节的输入流转化为字符的输入流
 *    OutputStreamWriter:将一个字符的输出流转化为字节的输出流
 * 2、作用:提供字节流与字符流之间的转换
 * 3、解码:字节、字节数组----->字符数组、字符串
 *    编码:字符数组、字符串----->字节、字节数组
 * 4、字符集
 *
 * @author ASUS
 * date: 2021/1/7 17:02
 * @since JDK 1.8
 */
/**
     * InputStreamReader的使用,实现了字节的输入流到字符的输入流的转换
     * @throws IOException
     */
    @Test
    public void test1() throws IOException {
        FileInputStream fis = new FileInputStream("hello.txt");
        //参数2指定了字符集,具体使用了那个字符集,取决于文件hello.txt保存时使用的字符集
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");

        char[] cbuf = new char[20];
        int len;
        while ((len = isr.read(cbuf)) != -1){
            String str = new String(cbuf, 0, len);
            System.out.print(str);
        }
        isr.close();
    }

标准的输入输出流

/**
 * 1、标准的输入,输出流
 * 1.1
 * System.in:标准的输入流,默认从键盘输入
 * System.out:标准的输出流,默认从控制台输出
 * 1.2
 * System类的setIn(InputStream is)  /  setOut(PrintStream ps)方式重新指定输入和输出的流
 * <p>
 * 1.3练习
 * 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入"e"
 * 或者"exit"时,退出程序。
 * <p>
 * 方法一:使用Scanner实现,调用next()返回一个字符串
 * 方法二:使用System.in实现。System.in---->转换流 ---->BufferedReader的readLine()
 *
 * @author ASUS
 * date: 2021/1/7 17:23
 * @since JDK 1.8
 */
public class OtherStreamTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            InputStreamReader isr = new InputStreamReader(System.in);
            br = new BufferedReader(isr);
            while (true) {
                System.out.println("请输入字符串:");
                String data = br.readLine();
                if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                    System.out.println("程序结束");
                    break;
                }
                String u = data.toUpperCase();
                System.out.println(u);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {

                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }

}

打印流

 /**
     * 打印流:PrintStream和PrintWriter
     * 1、提供了一系列重载的print()和println()
     *
     */
    @Test
    public void test(){
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("hello.txt"));
            //创建打印输出流,设置为自动刷新模式
            ps = new PrintStream(fos,true);
            if (ps != null){
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) {//输出ASCII字符
                System.out.print((char)i);
                if (i % 50 == 0){
                    System.out.println();//换行
                }
            }
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }finally {
            if (ps != null){
                ps.close();
            }
        }
    }

数据流

/**
 * 数据流
 * DataInputStream和DataOutputStream
 * 作用:用于读取或写出基本数据类型的变量或字符串
 */
    @Test
    public void test1() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        dos.writeUTF("邱周童");
        dos.flush();//刷新操作会将内存中的数据写入文件
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();

        dos.close();
    }

    /**
     * 将文件中存储的基本数据类型和字符串读取到内存中,保存在变量中
     * 注意点:读取不同类型的数据的顺序要当初写入文件时,保存的数据顺序要一致。
     * @throws IOException
     */
    @Test
    public void test2() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));

        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();

        System.out.println("name=" + name);
        System.out.println("age=" + age);
        System.out.println("isMale = " + isMale);

        dis.close();
    }

对象流

 /**
     * 序列化过程:将内存中的java对象存到磁盘中或通过网络传输出去
     * 使用ObjectOutPutStream实现
     */
    @Test
    public void test(){
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new String("我爱北京天安门"));
            oos.flush();//刷新操作
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 反序列化:将磁盘文件中的对象还原为内存中的一个java对象
     * 使用ObjectInputStream
     */
    @Test
    public void test1(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();
            String str = (String) obj;
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {

                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

自定义类

/**
 * Person需要满足如下的要求,方可序列化:
 * 1、需要实现接口Serializable接口
 * 2、当前类提供一个全局常量:serialVersionUID
 * 3、除了当前Person类需要实现Serializable接口之外,
 *    还必须保证其内部所有属性也必须是可序列化的。(默认情况下。基本数据类型都是可序列化的)
 * 补充:ObjectInputStream和ObjectOutputStream不能序列化static和transient修饰的成员变量
 *
 *
 * @author ASUS
 * date: 2021/1/8 14:38
 * @since JDK 1.8
 */
public class Person implements Serializable {
    public static final long serialVersionUID = 34234232234L;

    private String name;
    private int age;

    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 Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }
}












public class ObjectInputOutputStreamTest {
    /**
     * 序列化过程:将内存中的java对象存到磁盘中或通过网络传输出去
     * 使用ObjectOutPutStream实现
     */
    @Test
    public void test(){
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new String("我爱北京天安门"));
            oos.flush();//刷新操作

            oos.writeObject(new Person("王铭",23));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 反序列化:将磁盘文件中的对象还原为内存中的一个java对象
     * 使用ObjectInputStream
     */
    @Test
    public void test1(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();
            String str = (String) obj;

            Person p = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {

                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

随机存取文件流

/**
 *RandomAccessFile的使用
 *1、RandomAccessFile直接继承于java.lang.Object类,实现了DataInput,DatsOutput接口
 *2、RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
 *3、如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建
 *  如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
 *4、可以通过相关的操作seek(),实现RandomAccessFile“插入”数据的效果
 *
 */
public class RandomAccessFileTest{
    @Test
    public void test(){
        RandomAccessFile raf1 = new RandomAccessFile(new File("face.jpg","r"));
        RandomAccessFile raf2 = new RandomAccessFile(new File("face.jpg","rw"));
        
        byte[] buffer = new byte[1024];
        int len;
        while((len = raf1.read(buffer) != -1)){
            raf2.write(buffer,0,len);
        }
        raf1.close();
        raf2.close();
}
    
    @Test
    public void test(){
       RandomAccessFile raf1 new RandomAccessFile("hello.txt","rw");q
       raf1.write("xyz".getBytes());
       raf1.close();
    }
}
    

网络编程

网络编程中有两个主要的问题:

1、如何准确的定位一台或多台主机;定位主机上的特定的应用

2、找到主机后如何可靠高效的进行数据传输

网络编程中的两个要素:

1、对应问题一:IP和端口号

2、对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)

通信要素一:IP和端口号

1、IP:唯一的标识Internet上的计算机(通信实体)

2、在Java中使用InetAddress类代表IP

3、IP分类:IPv4和IPv6;万维网和局域网

4、域名

5、本地回路地址:127.0.0.1对应着:localhost

6、如何实例化InetAddress:两个方法:getByName(String host)、getLocalHost()

​ 两个常用方法:getHostName()/getHostAddress()

7、端口号:正在计算机上运行的进程。

要求:不同的进程有不同的端口号

范围:被规定为一个16位的整数0-65535.

8、端口号与IP地址的组合得出一个网络套接字:Socket

TCP的网络编程

/**
 * 实现TCP的网络编程
 * 例题:客户端发送文件给服务端,服务端将文件保存在本地。并返回发送成功给客户端。
 *
 * @author ASUS
 * date: 2021/1/9 13:48
 * @since JDK 1.8
 */
public class TCPTest2 {

    @Test
    public void client() throws IOException {
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);

        OutputStream os = socket.getOutputStream();

        FileInputStream fis = new FileInputStream(new File("face.jpg"));
        
        byte[] buffer = new byte[1024];
        int len;
        
        while((len = fis.read(buffer)) != -1){
            os.write(buffer,0,len);
        }
        
        //关闭数据的输出
        socket.shutdownOutput();
        
        //接收来自于服务器端的数据,并显示到控制台上
        InputStream is = socket.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len1;
        while((len1 = is.read(buffer)) != -1){
            baos.write(buffer,0,len1);
        }
        
        
        fis.close();
        os.close();
        socket.close();
    }

    @Test
    public void server() throws IOException {

        ServerSocket ss = new ServerSocket(9090);

        Socket socket = ss.accept();

        InputStream is = socket.getInputStream();
        
        FileOutputStream fos = new FileOutputStream(new File("hello.jpg"));

        byte[] buffer = new byte[1024];
        
        int len;
        
        while ((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        
        //服务端给予客户端反馈
        OutputStream os = socket.getOutputStream();
        os.write("你好,已收到照片!".getBytes());
        
        fos.close();
        is.close();
        socket.close();
        ss.close();

    }
}

UDP的网络编程

/**
 * UDPd协议的网络编程
 *
 * @author ASUS
 * date: 2021/1/9 14:23
 * @since JDK 1.8
 */
public class UDPTest {

    //发送端
    @Test
    public void sender() throws IOException {
        DatagramSocket socket = new DatagramSocket();

        String str = "我是UDP方式发送的导弹";
        byte[] data = str.getBytes();
        InetAddress inetAddress = InetAddress.getLocalHost();
        DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddress,9090);
        socket.send(packet);
        socket.close();
    }
    //接收端
    @Test
    public void receiver() throws IOException{
        DatagramSocket socket = new DatagramSocket(9090);
        byte[] buffer = new byte[100];
        DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
        
        socket.receive(packet);

        System.out.println(new String(packet.getData(),0,packet.getLength()));
        
        socket.close();
    }


}

URL的网络编程

/**
 * URL网络编程
 * 1、URL:统一资源定位符,对应着互联网的某一资源地址
 * 2、格式:
 * http://localhost:8080/xxx
 * 
 * @author ASUS
 * date: 2021/1/9 14:43
 * @since JDK 1.8
 */
public class URLTest {

    public static void main(String[] args) throws IOException {
       URL url = new URL("http://localhost:8080/");
       HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
       
       urlConnection.connect();

        InputStream is = urlConnection.getInputStream();
        FileOutputStream fos = new FileOutputStream("beauty3,jpg");

        byte[] buffer = new byte[1024];
        int len;
        
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        
        //关闭资源
        is.close();
        fos.close();
        urlConnection.disconnect();
    }
    
}

反射

Java反射机制提供的功能

1、在运行时判断任意一个对象所属的类

2、在运行时构造任意一个类的对象

3、在运行时判断任意一个类所具有的成员变量和方法

4、在运行时获取泛型信息

5、在运行时调用任意一个对象的成员变量和方法

6、在运行时处理注解

7、生成动态代理

/**
 * @author ASUS
 * date: 2021/1/10 13:08
 * @since JDK 1.8
 */
public class ReflectionTest {
    //反射之前,对于Person类的操作
    @Test
    public void test(){
        //1、创建Person类的对象
        Person p1 = new Person("Tom",12);

        p1.age = 10;

        System.out.println(p1.toString());

        p1.show();

        //在Person类外部,不可以通过Person类的对象调用其内部私有结构。
    }
    //反射以后,对于Person的操作
    @Test
    public void test1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Class clazz = Person.class;
        //1、通过反射,创建Person类的对象
        Constructor cons = clazz.getConstructor(String.class, int.class);

        Object obj = cons.newInstance("Tom", 12);
        Person p = (Person) obj;
        System.out.println(p.toString());

        //2、通过反射,调用对象指定的属性,方法
        Field age = clazz.getDeclaredField("age");
        age.set(p,10);
        System.out.println(p.toString());

        //调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(p);

        System.out.println("*******************************");

        //通过反射,可以调用Person类的私有结构,比如:私有的构造器,方法,属性
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1 = (Person) cons1.newInstance("Jerry");
        System.out.println(p1);

        //通过私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"QZT");
        System.out.println(p1);

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

关于java.lang.Class类的理解

1、类的加载过程:

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

2、换句话说,Class的实例就对应着一个运行时类。

3、加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

获取Class的实例的方式

//获取Class的实例的方式
    @Test
    public void test2() throws ClassNotFoundException {
        //方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.cs.java.Person");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);
        
        //方式四:使用类的加载器:ClassLoader
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.cs.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);
    }

类的加载器

 /**
     * 读取配置文件
     * @throws IOException
     */
    @Test
    public void test3() throws IOException {
        Properties prop = new Properties();
        //此时的文件默认在当前的module下
        //方式一
        /*FileInputStream fis = new FileInputStream("jdbc.properties");
        prop.load(fis);*/

        //方式二:使用ClassLoader
        //配置文件默认识别为:当前module的src下
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc.properties");
        
        prop.load(is);

        String user = prop.getProperty("user");
        String password = prop.getProperty("password");
        System.out.println("user=" + user + "password=" + password);
    }

通过反射创建类的对象

/**
 * 通过反射创建对应的运行时类的对象
 * 
 * 
 * @author ASUS
 * date: 2021/1/11 13:47
 * @since JDK 1.8
 */
public class NewInstanceTest {
    @Test
    public void test1() throws IllegalAccessException, InstantiationException {
        Class clazz = Person.class;
        /**
         * newInstance():调用此方法,创建对应的运行时类的对象
         * 
         * 要想此方法正常的创建运行时类的对象,要求:
         * 1、运行时类必须提供空参的构造器
         * 2、空参的构造器的访问权限得够。通常,设置为public。
         * 
         * 在javabean中要求提供一个public的空参构造器。原因:
         * 1、便于通过反射,创建运行时类的对象
         * 2、便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
         */
        Person obj = (Person) clazz.newInstance();
        System.out.println(obj);
    }

反射的动态性

@Test
    public void test2(){
        for (int i = 0; i < 100; i++) {
            int num = new Random().nextInt(3);//0,1,2
            String classpath = "";
            switch (num){
                case 0:
                    classpath = "java.util.Date";
                    break;
                case 1:
                    classpath = "java.lang.Object";
                    break;
                case 2:
                    classpath = "com.cs.java.Person";
                    break;
            }
            try {
                Object obj = getInstance(classpath);
                System.out.println(obj);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    /*
    * 创建一个指定类的对象
    * */
    public Object getInstance(String classpath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName(classpath);
        return clazz.newInstance();
    }

动态代理

静态代理

/**
 * 静态代理举例
 * 特点:代理类和被代理类在编译期间,就确定下来了。
 *
 *
 * @author ASUS
 * date: 2021/1/11 14:49
 * @since JDK 1.8
 */
public interface ClothFactory {
    void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory){
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做一些后续收尾工作");
    }

}

//被代理类
class NikeClothFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("Nike工厂生产一批运动服");
    }
}

public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        NikeClothFactory nike = new NikeClothFactory();

        //创建代理类的对象
        ProxyClothFactory clothFactory = new ProxyClothFactory(nike);

        clothFactory.produceCloth();
    }
}

动态代理

/**
 * 动态代理举例
 *
 * @author ASUS
 * date: 2021/1/11 15:02
 * @since JDK 1.8
 */
interface Human {

    String getBelief();

    void eat(String food);

}

//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃" + food);
    }

}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    //当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能声明在invoke()中
    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);
        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;
    }
}

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("粑粑");
    }
}

()时,保证父类有此构造器
*/
Person obj = (Person) clazz.newInstance();
System.out.println(obj);
}




## 反射的动态性

```java
@Test
    public void test2(){
        for (int i = 0; i < 100; i++) {
            int num = new Random().nextInt(3);//0,1,2
            String classpath = "";
            switch (num){
                case 0:
                    classpath = "java.util.Date";
                    break;
                case 1:
                    classpath = "java.lang.Object";
                    break;
                case 2:
                    classpath = "com.cs.java.Person";
                    break;
            }
            try {
                Object obj = getInstance(classpath);
                System.out.println(obj);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    /*
    * 创建一个指定类的对象
    * */
    public Object getInstance(String classpath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName(classpath);
        return clazz.newInstance();
    }

动态代理

静态代理

/**
 * 静态代理举例
 * 特点:代理类和被代理类在编译期间,就确定下来了。
 *
 *
 * @author ASUS
 * date: 2021/1/11 14:49
 * @since JDK 1.8
 */
public interface ClothFactory {
    void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory){
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做一些后续收尾工作");
    }

}

//被代理类
class NikeClothFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("Nike工厂生产一批运动服");
    }
}

public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        NikeClothFactory nike = new NikeClothFactory();

        //创建代理类的对象
        ProxyClothFactory clothFactory = new ProxyClothFactory(nike);

        clothFactory.produceCloth();
    }
}

动态代理

/**
 * 动态代理举例
 *
 * @author ASUS
 * date: 2021/1/11 15:02
 * @since JDK 1.8
 */
interface Human {

    String getBelief();

    void eat(String food);

}

//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃" + food);
    }

}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    //当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能声明在invoke()中
    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);
        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;
    }
}

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("粑粑");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值