Java——其他知识汇总(更新中...)

一、代理(功能增强)

代理:为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。

简记:我现在有个登录的功能(被代理类,旧了,不想要了),但我现在又不想完全修改以前的代码,就写个代理类,在代理类里添加了对地区的验证、对登录次数的验证等等功能,再调用被代理类,这样就实现了代理!

在不改变原来的目标方法前提下,通过代理实现自己的功能代码!
在这里插入图片描述

静态代理

需要:接口、被代理类、代理类
关系:被代理类 + 代理类 —>实现了接口

举例:
接口:

public interface HelloInterface {
    void sayHello();
}

被代理类:(旧功能,希望更新的)

public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}

代理类:

public class HelloProxy implements HelloInterface{
里面存在 被代理类的对象
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

静态代理:很容易完成对类的代理,但缺点是只能对一个类进行代理,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

动态代理

为了解决静态代理的“一对一”的问题,利用反射实现动态代理。
被代理类(公司)和接口(卖U盘)都和上面一样
代理类(微商)需要实现InvocationHandler接口

InvocationHandler接口:
	仅一个方法invoke()
	重写invoke()方法:代理对象需要执行的功能代码,代理类要完成的功能就写在invoke方法中
	代理类要完成的功能:调用代理类的方法+功能增强(比如中间商加价)
	
	public Object invoke(Object proxy, Method method, Object[] args) :
		三个参数:
			 Object proxy:创建的代理对象:newProxyInstance()这个静态方法返回代理对象,等同于静态代理中的new factory();
			 Method method:目标类的方法
			 Object[] args:目标类的方法的参数
	
public static Object newProxyInstance(ClassLoader loader,class<?>[] interfaces, InvocationHandler h)
		三个参数:
		ClassLoader loader:类加载器,使用反射获取
		class<?>[] interfaces:目标对象实现的接口,也是反射获取
		**InvocationHandler h:自己写的,代理类要完成的功能**
  1. jdk动态代理:必须有接口,目标类(被代理类)必须实现接口
  2. cglib动态代理:可以没有接口

JDK动态代理过程

public class ProxyHandler implements InvocationHandler{
    private Object object;
    //动态代理,目标对象是活动的,需要传入进来
    public ProxyHandler(Object object){
    	//传入对象
        this.object = object;
    }
    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "  + method.getName());
        method.invoke(object, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
}

主函数这样:
其中的语句都是用了多态技术,即父类的引用指向子类的对象,接口也同理。

    public static void main(String[] args) {
        //创建目标对象
        HelloInterface hello = new Hello();
        //创建invocationHandler对象
        InvocationHandler handler = new ProxyHandler(hello);
		//创建代理对象
        HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
		//通过动态代理执行方法
        proxyHello.sayHello();
    }
输出:
    Before invoke sayHello
    Hello zhanghao!
    After invoke sayHello

这时又来了一个接口(功能)+被代理类(公司):

public interface ByeInterface {
    void sayBye();
}
public class Bye implements ByeInterface {
    @Override
    public void sayBye() {
        System.out.println("Bye zhanghao!");
    }
}

无需像静态代理一样重新去写,只需在主程序中这样:

    public static void main(String[] args) {
        //被代理类创建对象
        HelloInterface hello = new Hello();
        ByeInterface bye = new Bye();
		//InvocationHandler 接口:写好新加的业务逻辑
        InvocationHandler handler = new ProxyHandler(hello);
        InvocationHandler handler1 = new ProxyHandler(bye);

        HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);

        ByeInterface proxyBye = (ByeInterface) Proxy.newProxyInstance(bye.getClass().getClassLoader(), bye.getClass().getInterfaces(), handler1);
        proxyHello.sayHello();
        proxyBye.sayBye();
    }
    输出:
    Before invoke sayHello
    Hello zhanghao!
    After invoke sayHello
    Before invoke sayBye
    Bye zhanghao!
    After invoke sayBye

在这里插入图片描述
好处:不用再写代理类(静态代理中的)了,当接口增加方法时,影响的比较小,不像静态代理一样所有的代理类都要重新写“override”。

四、设计模式

1. 适配器模式

便于复用。版本向下兼容时

使用继承

在这里插入图片描述
即,当我们有一个旧版本的代码,又想实现新接口的功能时,这时可以继承旧版本代码,并实现新接口,这样就既能保证旧版本的兼容,又能开启新功能。

使用委托

在这里插入图片描述
思想是相同的,改变的是,这时Print不是接口而是,只需在新代码中继承这个类,再在新代码中生成一个旧代码的对象,再调取他的功能。

2. 单例模式

确保某个类只有一个实例。Spring下默认的bean均为singleton,可以通过singleton=“true|false” 或者 scope="?"来指定。

  1. 懒汉式,线程不安全
public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}
  1. 懒汉式,线程安全,但是效率不高
```java
public class Singleton {
    private static Singleton instance;
    private Singleton (){}

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

        return instance;

    }
}
  1. 饿汉式,线程安全,but不是懒加载
public class Singleton{
    //类加载时就直接初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}
  1. 枚举类,默认线程安全,且能防止反序列化造对象
public enum EasySingleton{
    INSTANCE;
}

运用jad进行反编译可以看到:
在这里插入图片描述
我们可以看到,枚举单例的本质就是饿汉式单例,即在 static 代码块完成了实例的创建及初始化。

3. 代理模式

面向切面编程AOP:在方法执行前后可以创建其他有意义的操作。
看第一部分!
spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。

4. 模板方法模式

在父类中定义处理流程的框架,在子类中实现具体处理

  • 抽象类:定义抽象方法不给出具体的实现
  • 具体类:负责实现抽象类中的抽象方法

好处:
①逻辑处理通用:父类中模板方法编写了算法,无需在每个子类中编写,后续修改也容易。
②使用父类类型变量保存子类实例:即使没有指定子类的种类,程序也能正常工作。

举例:Java中的inputStream使用了模板模式。
在这里插入图片描述

5. 工厂方法模式(模板方法的典型应用)

类似模板模式,父类决定实例的生成方式,但并不决定所要生成的具体的类,子类负责具体
应用程序直接使用new创建新的对象,为了将对象的创建使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。
在这里插入图片描述

不用new关键字来生成实例,而是调用生成实例的专用方法来生成实例,防止父类与子类耦合

举例:
spring的工厂方法模式使用BeanFactory 或 ApplicationContext 创建 bean 对象。
在这里插入图片描述

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("config.xml"));       

假设有这样一个factory对象,可以直接factory.getBean("xxxxxxx")就返回工厂xxxxxxx的Bean的实例,也就是所谓的工厂方法模式!

6. 原型模式(克隆模式)

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

Java创建对象
new操作符
clone方法

① new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。
分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
② clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

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

深拷贝or浅拷贝
以上的Person类是这样的

public class Person implements Cloneable{
	
	private int age ;
	private String name;
	
	public Person(int age, String name) {
		this.age = age;
		this.name = name;
	}
	
	public Person() {}
 
	public int getAge() {
		return age;
	}
 
	public String getName() {
		return name;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return (Person)super.clone();
	}
}

在这里插入图片描述
clone方法属于浅拷贝
如果想实现深拷贝就需要实现clonable接口并重写clone方法。
在这里插入图片描述
但是并没有实现真正的深拷贝,因为对于head来说还是浅拷贝的,head中若存在其他对象,那么其他对象也是浅拷贝的在这里插入图片描述

7. 装饰者模式(decorator模式)

在这里插入图片描述
StringDisplay就是源代码,Border是对于此源代码的所有装饰类的抽象类,其内部保存了被装饰的对象,所有这些类的构造器内都是(Display display)。

优点:
①接口的透明性——由于装饰类与原类都继承了同一抽象类,这样即使被包装起来,其API也依旧能被调用。由于接口的透明性,也可以采用递归结构多层装饰。new FullBoarder (newStringDisplay(new ...))
②可以在不改变被装饰物的前提下增加功能——被装饰物与装饰物的API相同。

举例:
java.io包中的new BufferedReader(new FileReader()),都继承了Read抽象类。

继承与委托:
继承即Parent obj = new Child(),可以用Parent的API。
委托是现在存在两个类,其中一个类中创建了另一个类的对象,利用此对象的方法完成自己的方法。

8. 观察者模式

当观察对象发生变化时,会通知给观察者。
在这里插入图片描述

成员
被观察者subject:用一个List保存观察者,notifyObservers()方法调用所有观察者.update
观察者observers:所有观察者实现Observer接口(重写其update方法),注意Observer接口的update传进去的参数是被观察者类型
在这里插入图片描述
在这里插入图片描述
由于被观察者的notifyObservers()会将this传给观察者的update()方法,做到将被观察者通知给观察者。

注意:
①如果observers的update的方法又更改了subject,这样就会产生循环调用。
②java中提供了Observer接口(observers必须实现这个)和Observable类(subject必须继承这个),
存在问题,由于Java只能单一继承,不方便,最好不用这个。解决方法:自己将subject和observers都定义为接口。

9. 责任链模式

多个对象组成一条责任链,当某人被要求做什么,自己能做就做,做不了就转给另一个人,下一个人也这样
优点:
①弱化了发出请求的人和处理请求的人的关系——如果不用这个,就必须有个人专门为了分配任务。
②可动态改变责任链。
缺点:
带来一点点的延迟,如果请求和处理者之间的关系是确定的,而且需要非常快的处理速度时,这模式不太行。

责任链模式举例:

拦截器模式

在这里插入图片描述

  • 调度器
    调度器调度N个拦截器
  • 拦截器
    只是负责拦截业务代码,在业务代码前后做一些操作,拦截器不会直接调用业务代码(不然会耦合),拦截器只是在处理完自己的逻辑后通知调度器,由调度器负责后续的操作(执行业务逻辑或其他拦截器)。
  • 业务逻辑
    原本的代码

10. 抽象工厂模式

工厂模式:简单工厂模式、工厂方法模式抽象工厂模式

在这里插入图片描述
工厂方法模式产出的是产出是一个产品(实例),抽象工厂产出是一个抽象(接口)。区别在于,若添加一个新的产品,前者是修改工厂,后者是创建新工厂(符合“闭合原则”)

五、String不可变类型

JDK7中String类型:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
 
    /** Cache the hash code for the string */
    private int hash; // Default to 0

String的成员变量是private final 的,也就是初始化之后不可改变。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗——只能通过反射改变!
在这里插入图片描述

	public static void testReflection() throws Exception {
		
		//创建字符串"Hello World", 并赋给引用s
		String s = "Hello World"; 
		
		System.out.println("s = " + s);	//Hello World
		
		//获取String类中的value字段
		Field valueFieldOfString = String.class.getDeclaredField("value");
		
		//改变value属性的访问权限
		valueFieldOfString.setAccessible(true);
		
		//获取s对象上的value属性的值
		char[] value = (char[]) valueFieldOfString.get(s);
		
		//改变value所引用的数组中的第5个字符
		value[5] = '_';
		
		System.out.println("s = " + s);  //Hello_World
	}

六、IO多路复用

1)BIO

服务端处理客户端的请求

listenfd = socket();   // 打开一个网络通信端口
bind(listenfd);        // 绑定
listen(listenfd);      // 监听
while(1) {
  connfd = accept(listenfd);  // 阻塞建立连接
  int n = read(connfd, buf);  // 阻塞读数据
  doSomeThing(buf);  // 利用读到的数据做些什么
  close(connfd);     // 关闭连接,循环等待下一个连接
}

在这里插入图片描述
在这里插入图片描述
这个连接的客户端一直不发数据,那么服务端线程将会一直阻塞在 read 函数上不返回,也无法接受其他客户端连接。

这肯定是不行的。

2)NIO

需要操作系统提供一个read函数的非阻塞形式的函数。这个 read 函数的效果是,如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待。
在这里插入图片描述

3)IO多路复用

用一个线程就可以监控多个文件描述符。

select

select 是操作系统提供的系统调用函数,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理:
在这里插入图片描述

  1. select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
  2. select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)
  3. select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)

poll

与select的区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。为什么?底层是链表。其余无区别。

epoll

针对select的三个缺陷:

  1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可
  2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒
  3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。
    在这里插入图片描述

七、线程池ThreadPoolExecutor

在这里插入图片描述

1)线程池流程

在这里插入图片描述

2)线程池的五种状态

RUNNING状态可以接受新进来的任务,同时也会执行队列里的任务。

SHUTDOWN 状态已经不会再接受新任务,但仍旧会处理队列中的任务。

STOP状态在之前的基础上,不会处理队列中的任务,在执行的任务也会直接被打断。

TIDYING状态在之前的基础上,所有任务都已经终止,池中的Worker线程都已经为0,也就是stop状态在清理完所有工作线程之后就会进入该状态,同时在shutdown状态在队列空以及工作线程清理完毕之后也会直接进入这个阶段,这一阶段会循环执行terminated()方法。

TERMINATED 状态作为最后的状态,在之前的基础上terminated()方法也业已执行完毕,才会从上个状态进入这个状态,代表线程池已经完全停止。

3)状态底层采用的是AtomicInteger

通过比较大小得知线程池的状态。

4)初始化线程池需要的变量

corePoolSize:线程池的基本大小
maximumPoolSize:代表线程池中最大的工作线程数量
keepAliveTime为线程池中工作线程数量大于corePoolSize时,每个工作线程的在空闲时最长的等待时间。

workQueue作为线程池的任务等待队列
Workers作为存放工作线程的HashSet。

Handler作为线程池中在不能接受任务的时候的拒绝策略,我们可以实现自己的拒绝策略,在实现了RejectedExecutionHandler接口。

八、线程安全

在这里插入图片描述

JVM中的线程安全

jvm有一个main memory,而每个线程有自己的working
memory,一个线程对一个variable进行操作时,都要在自己的working
memory里面建立一个copy,操作完之后再写入main
memory;而用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其他你认为合适的object比如method,然后通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完
load到workingmemory -> use&assign -> store到mainmemory
的过程,才会释放它得到的锁。这样就实现了所谓的线程安全。

  • list接口

Vector、ArrayList、LinkedList
Vector线程安全,synchronized修饰;ArrayList、LinkedList线程不安全
ArrayList:数组结构,查询方便,增加删除需要后移
LinkedList:链表结构,查询不方便,增加删除简单

  • Map和Set接口:

HashTable,HashMap,HashSet

HashMap是非线程安全的,HashTable是线程安全的,内部的方法基本都是synchronized,HashTable不允许null值(put的key如果null直接抛出异常)。
HashSet:
1、HashSet基于HashMap实现,无容量限制。
2、HashSet是非线程安全的。
3、HashSet不保证有序。

HashMap:
1、HashMap采用数组方式存储key,value构成的Entry对象,无容量限制。
2、HashMap基于Key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式来解决。
3、HashMap在插入元素时可能会要扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中。
4、HashMap是非线程安全的。
5、HashMap遍历使用的是Iterator。

HashTable
1、HashTable是线程安全的。
2、HashTable中无论是Key,还是Value都不允许为null。
3、HashTable遍历使用的是Enumeration。
TreeMap:
1、TreeMap是一个典型的基于红黑树的Map实现,因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。
2、TreeMap是非线程安全的。
TreeSet:
1、TreeSet基于TreeMap实现,支持排序。
2、TreeSet是非线程安全的。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
线程安全的实现:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
比如i++操作在多线程时不是线程安全的。
在这里插入图片描述

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

public class TLS {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 设置当前线程的本地线程变量
                threadLocal1.set("thread1");
                threadLocal2.set(1);
                System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
                // 使用完毕后要删除,避免内存泄露
                threadLocal1.remove();
                threadLocal2.remove();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal1.set("thread2");
                threadLocal2.set(2);
                System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
                threadLocal1.remove();
                threadLocal2.remove();
            }
        });
        thread1.start();
        thread2.start();
        // 没有通过ThreadLocal为主线程添加过本地线程变量,获取到的内容都是null
        System.out.println(threadLocal1.get()+": "+threadLocal2.get());
    }
}

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

怎么理解ThreadLocal?
一个hashmap,key会自动帮你填好,因此直接set(value)就可以,不同的线程会提取到不同的value,即,将不同线程的数值都保存起来了。

九、枚举类

在这里插入图片描述

十、IO流

在这里插入图片描述

在这里插入图片描述
IO流用了装饰者模式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值