Java知识回顾(二)

泛型是什么

集合存储对象并在使用先进行类型转换很不方便,泛型是为了那种情况的发生。他提供了编译器的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。

泛型如何工作

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有相关类型的信息,所以在运行时不存在任何类型的相关信息。

泛型中的限定通配符和非限定通配符

有两种限定通配符

一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界

一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界

另一方面<?>表示了非限定通配符,<?>可以用任意类型来替代。

编写泛型方法

泛型方法需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。

public V put(K key, V value) {
	return cache.put(key, value);
}

异常

异常是在程序执行期间可能发生的错误事件,并且会中断它的正常流程。

异常关键字

  • throw——throw关键字用于向运行时抛出异常来处理它。
  • throws——在方法中抛出任何已检查的异常而不处理它时,我们需要在方法签名中使用throws关键字让调用者程序知道该方法可能抛出的异常。
  • try-catch——在代码中使用try-catch块进行异常处理
  • finally——finally块总是被执行,无论是否发生异常。

异常的层次结构

Throwable是异常的父类,他有Error和Exception两个子类。

Error 是超出应用程序范围的特殊情况,并且无法预测并从中恢复,例如硬件故障,JVM崩溃或内存不足错误。

Exception 是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。运行时异常是由错误的编程引起的,例如尝试从Array中检索超出下标的元素。

throw和throws

throws关键字与方法签名一起用于声明方法可能抛出的异常,而throw关键字用于破坏程序流并将异常对象移交给运行时来处理它。

OutOfMemoryError

Java中的OutOfMemoryError是java.lang.VirtualMachineError的子类,当JVM用完堆内存时,它会抛出它。

注解

注解是绑定到程序源代码元素的元数据,对运行​​代码的操作没有影响。

Java注解又称为标注,是支持加入源码的特殊语法元数据;Java中的类、方法、变量、参数、包都可以被注解。这里提到的元数据是描述数据的数据。

典型用例:

  • 编译器的信息 ——使用注解,编译器可以检测错误或抑制警告
  • 编译时处理——软件工具可以处理注解并生成代码,配置文件等
  • 运行时处理——可以在运行时检查注解以自定义程序的行为

注解分类

标准注解:包括 Override, Deprecated, SuppressWarnings

标准 Annotation 是指 Java 自带的几个 Annotation。

上面三个分别表示重写函数不鼓励使用忽略Warning

元注解

1. @Retention

用于指定被修饰的注解可以保留多长时间,只能修饰Annotation定义。

  • RetentionPolicy.CLASS——编译器将把Annotation记录在class文件中。当运行java程序时,JVM不可获取Annotation信息。
  • RetentionPolicy.RUNTIME——编译器将把Annotation记录在class文件中。当运行java程序时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息。
  • RetentionPolicy.SOURCE: Annotation只保留在源代码中(.java文件中),编译器直接丢弃这种Annotation。

@Target

用于指定被修饰的Annotation能用于修饰哪些程序单元。

  • @Target(ElementType.ANNOTATION_TYPE): 指定该策略的Annotation只能修饰Annotation。
  • @Target(ElementType.TYPE) : 接口、类、枚举、注解
  • @Target(ElementType.FIELD) : 成员变量(字段、枚举的常量)
  • @Target(ElementType.METHOD) : 方法
  • @Target(ElementType.PARAMETER): 方法参数
  • @Target(ElementType.CONSTRUCTOR): 构造函数
  • @Target(ElementType.LOCAL_VARIABLE): 局部变量
  • @Target(ElementType.PACKAGE): 修饰包定义

3. Documented

用于指定被修饰的Annotation将被javadoc工具提取成文档。即说明该注解将被包含在javadoc中。

4. @Inherited

用于指定被修饰的Annotation具有继承性。即子类可以继承父类中的该注解。

5. Repeatable

表示这个注解可以在同一处多次声明

反射

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

通过反射可以在程序运行时动态创建对象并调用类中的方法。

Java的反射机制可以做3件事:运行时创建对象、运行时调用方法、运行时读写属性。

动态代理

代理类在程序运行时创建的代理方式被称为动态代理。
代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的指示动态生成的。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

例如:

在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。

public class Vendor implements Sell {
    public void sell() {
        System.out.println("In sell method");
    }
    public void ad() {
        System, out.println("ad method")
    }
} 

使用静态代理

public class BusinessAgent implements Sell {
    private Vendor mVendor;
    public BusinessAgent(Vendor vendor) {
        this.mVendor = vendor;
    }
    public void sell() {
        System.out.println("before");
        mVendor.sell();
        System.out.println("after");
    }

    public void ad() {
        System.out.println("before");
        mVendor.ad();
        System.out.println("after");
    }
} 

动态代理

需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口。

public class DynamicProxy implements InvocationHandler {

    private Object obj; //obj为委托类对象; 
    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(obj, args);
        System.out.println("after");
        return result;
    }

} 
public class Main {
    public static void main(String[] args) {
        //创建中介类实例 
        DynamicProxy inter = new DynamicProxy(new Vendor());
        //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件 
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //获取代理类实例sell 
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, inter));
        //通过代理类对象调用代理类方法,实际上会转到invoke方法调用 
        sell.sell();
        sell.ad();
    }
} 

线程进程区别

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。

进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。

线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位,同一进程中的多个线程之间可以并发执行。

守护线程

用户线程:平常创建的普通线程
守护线程:用来服务于用户线程;不需要上层逻辑介入。

当线程只剩下守护线程的时候,JVM就会退出;如果还有其他的任意一个用户线程还在,JVM就不会退出。守护线程最典型的例子就是GC线程。

多线程上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

创建线程的方式

  • 实现Runnable——实现Runnable接口的类还可能扩展另一个类.
  • 扩展Thread——扩展Thread类就代表这个子类不能扩展其他类
  • 通过Callable和FutureTask创建线程——有返回值

FutureTask

FutureTask表示一个异步运算的任务。

可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。

wait()与sleep()的区别

  • 调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
  • sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU
  • sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用

synchronized和ReentrantLock的区别

synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。

ReentrantLock是类,它提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量。

扩展性体现在:

  • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
  • ReentrantLock可以获取各种锁的信息
  • ReentrantLock可以灵活地实现多路通知
  • 二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word.

两个线程间共享数据

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。

ThreadLocal

ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值