反射

动态语言, 是指程序在运行时可以改变其结构: 新的函数可以引进, 已有的函数可以被删除等结构上的变化. JavaScript,Ruby,python等属于动态语言, C, C++则不属于动态语言. 从反射的角度来说,java属于半动态语言

反射机制的概念: 运行状态中知道类所有的属性和方法

Java的反射机制是指在运行状态中, 对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象, 都能够调用它的任意一个方法;

编译时类型和运行时类型

在Java程序中许多对象在运行时都会出现两种类型:

  1. Person person=new Student();
  1. 编译时类型: 由声明对象时使用的类型来决定, 如上面的Person.
  2. 运行时类型: 由实际赋值给对象的类型决定,如上面的Student.

所以程序在运行的时候, 可能要接受未知的对象, 不知道其类型, 所以要用到反射来获取其信息.

Java反射 API

  1. Class类: 反射的核心类, 可以获取类的信息,方法等
  2. Field类: Java.lang.reflec包中的类, 表示类的成员变量, 可以用来获取和设置类中的属性
  3. Method类: Java.lang.reflec包中的类,表示类的方法.
  4. Constructor类: Java.lang.reflec包中的类, 表示类的构造方法

获取Class对象的3中方法

Class类就是描述一个类的类.

  1. 调用对象的getClass()
  1. Class clazz=new Person().getClass();
  1. 调用类的class属性来获取
  1. Class clazz=Person.class;
  1. Class类中的forName()静态方法, 最安全, 性能最好
  1. Class clazz=Class.forName("类的全路径");

创建实例的两种方法

  1. Class对象的newInstance(), 要求Class对象有默认的空构造器
  2. 先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建Class对象对应类的实例. 通过这种方式可以选择不同的构造方法
    1. Class clazz=Class.forName("reflection.Person");
    2. Person p=(Person)clazz.newInstance();
    3. Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
    4. Person p=(Person)c.newInstance("可汗","男",20)';
  1. public class Apple {
  2. private int price;
  3. public int getPrice() {
  4. return price;
  5. }
  6. public void setPrice(int price) {
  7. this.price = price;
  8. }
  9. public static void main(String[] args) throws Exception{
  10. //正常的调用
  11. Apple apple = new Apple();
  12. apple.setPrice(5);
  13. System.out.println("Apple Price:" + apple.getPrice());
  14. //使用反射调用
  15. Class clz = Class.forName("com.chenshuyi.api.Apple");
  16. Method setPriceMethod = clz.getMethod("setPrice", int.class);
  17. Constructor appleConstructor = clz.getConstructor();
  18. Object appleObj = appleConstructor.newInstance();
  19. setPriceMethod.invoke(appleObj, 14);
  20. Method getPriceMethod = clz.getMethod("getPrice");
  21. System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
  22. }
  23. }

动态代理

通过一个方法就可以生成一个对指定接口的实现类对象

  1. public class Student implements StudentInterface{
  2. private String name;
  3. public Student(String name) {
  4. this.name = name;
  5. }
  6. public Student() {
  7. }
  8. @Override
  9. public void eat() {
  10. System.out.println(this.name+" eat.");
  11. }
  12. @Override
  13. public void study() {
  14. System.out.println(this.name+" study.");
  15. }
  16. }
  17. public interface StudentInterface {
  18. void eat();
  19. void study();
  20. }
  21. public class TestClazz {
  22. public static void main(String[] args) {
  23. Student student = new Student("Oddity");
  24. //实现一个StudentProxy代理对象
  25. StudentInterface StudentProxy = (StudentInterface) Proxy.newProxyInstance(student.getClass().getClassLoader(), student.getClass().getInterfaces(), new InvocationHandler() {
  26. @Override
  27. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  28. if (method.getName().equals("study")) {
  29. System.out.println("shit");
  30. return null;
  31. } else {
  32. return method.invoke(student, args);
  33. }
  34. }
  35. });
  36. StudentProxy.study();
  37. StudentProxy.eat();
  38. }
  39. }

Proxy的静态方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法. 实现了interfaces数组中的接口, 这个StudentProxy么有.java文件, 是在运行时生成的.

  1. ClassLoader loader: 类加载器, 需要代理的对象的类加载器: YourObject.class.getClassLoader
  2. Class[] interfaces: 指定要实现哪些接口
  3. InvocationHandler h: 是一个接口, 叫做调用处理器, 代理对象肯定是实现了你指定的接口, 调用代理对象的接口方法时 ,都是在调用InvocationHandlerinvoke()方法

    Java Web

保存的四个作用域

  1. Page级别, jsp使用, 现在已经不使用了
  2. Request级别, 在一个请求中范围内, 如果是sendRedirect重定向, 那么作用域中保存的值就失效了, 如果是重定向(request.getRequestDispatcher.forward()), 那么域中的值还能使用
  3. session会话级别, 再一次会话中域中的值有效, request.getSession.setAttribute()
  4. application级别, 在一个应用程序中, request.getServletContext.setAttribute

Java线程

进程: 每一个进程都有独立的代码和数据空间(进程上下文). 进程间的切换有较大的开销, 一个进程包括1-n个线程. 进程是资源分配的最小单位

线程: 同一类线程共享代码和数据空间, 每一个线程有独立的执行栈和程序计数器(PC), 线程切换的开销很小, 线程是CPU调度的基本单位

实现线程的两种方法

  1. 继承Thread类
    ```java package com.multithread.learning; /* @functon 多线程学习 @author 林炳文 @time 2015.3.9 */ class Thread1 extends Thread{ private String name; public Thread1(String name) { this.name=name; } public void run() {

    1. for (int i = 0; i < 5; i++) {
    2. System.out.println(name + "执行 : " + i);
    3. try {
    4. sleep((int) Math.random() * 10);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. }

    } } public class Main {

    public static void main(String[] args) {

    1. Thread1 mTh1=new Thread1("A");
    2. Thread1 mTh2=new Thread1("B");
    3. mTh1.start();
    4. mTh2.start();

    }

} 输出: A执行 : 0 B执行 : 0 A执行 : 1 A执行 : 2 A执行 : 3 A执行 : 4 B执行 : 1 B执行 : 2 B执行 : 3 B执行 : 4

再执行一下:

A执行 : 0 B执行 : 0 B执行 : 1 B执行 : 2 B执行 : 3 B执行 : 4 A执行 : 1 A执行 : 2 A执行 : 3 A执行 : 4

  1. 2. 实现java.lang.Runnable接口<br />重写run方法 <br />对于Runnable接口, 在启动的时候, 需要先通过Thread类的构造方法Thread(Runnable target)构造出对象, 再调用ThreadStart()方法开启线程.
  2. ```java
  3. /**
  4. *@functon 多线程学习
  5. *@author 林炳文
  6. *@time 2015.3.9
  7. */
  8. package com.multithread.runnable;
  9. class Thread2 implements Runnable{
  10. private String name;
  11. public Thread2(String name) {
  12. this.name=name;
  13. }
  14. @Override
  15. public void run() {
  16. for (int i = 0; i < 5; i++) {
  17. System.out.println(name + "执行 : " + i);
  18. try {
  19. Thread.sleep((int) Math.random() * 10);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  25. }
  26. public class Main {
  27. public static void main(String[] args) {
  28. new Thread(new Thread2("C")).start();
  29. new Thread(new Thread2("D")).start();
  30. }
  31. }

实际上全部的多线程代码都是通过执行Thread的start()方法来执行的, 不管是继承Thread类, 还是实现Runnable接口来实现多线程, 最终还是通过Thread的API来控制线程的.

Thread和Runnable的差别

如果继承了Thread类, 那么不适合资源的共享, 如果实现了Runnable接口, 非常容易实现

实现Runnable接口不继承Thread类所具有的优势:

  1. 适合多个同样的代码程序的线程去处理同一个资源
  2. 能偶避免Java中的单继承的限制
  3. 增加程序的健壮性, 代码能够呗多个线程共享, 代码和数据独立
  4. 线程池仅仅能放入Runnable或者callable类线程, 不能直接放入继承Thread的类

main程序其实也是一个线程, Java中所有的线程都是同一时间开启的,什么时候, 哪个执行, 取决于CPU的资源分配

每次程序的执行至少启动2个线程, 一个是main线程, 一个是垃圾收集线程

每当使用java命令执行一个类的时候, 实际上会启动一个JVM, 每一个JVM就是在操作系统中启动了一个进程.

Java - 图1

经常使用的函数

  1. sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
  2. join(): 指等待t线程终止
    如果子程序没有调用t.join()方法, 那么主程序可能在子程序之前执行完, 而调用以后, main程序会在子程序完成后再结束
  3. yield()是指当前的线程让出当前的CPU占有权, 让出的时间是不可设定的, 称为退让, 先检测是否有同样优先级的线程处于可执行状态, 如果, 那么就交给此线程, 否则执行原来的线程, 不可能让优先级低的线程来获取CPU的占有权, 但是Sleep()方法可以.

    Java线程的6个状态

    1. //Thread.State源码
    2. public enum State {
    3. NEW,
    4. RUNNABLE,
    5. BLOCKED,
    6. WAITING,
    7. TIMED_WAITING,
    8. TERMINATED;
    9. }

    1. NEW

    处于此状态表示没有启动. 指创建了Thread实例, 但是没有调用start()方法
    Question:
    反复调用一个线程的start()方法, 是否可行, 或者说一个线程执行完毕, 处于TERMINATED状态, 再次调用start()是否可行?
    Answer:
    都不可行, 第一次调用start()后会将ThreadStatus(原值为0)改为非零, 此时调用start()方法会抛异常.

    2. RUNNABLE

    此状态表示线程正在JVM中运行, 也可能在等待其他系统资源, 如果I/O
    包含了传统操作系统中的ready和running两个状态

    3. BLOCKED

    阻塞状态. 处于此状态的线程等待锁的释放以进入同步区

    你去只有一个窗口的食堂打饭, 前面已经有一个人T1线程, 你是T2线程. 此时T1线程占有锁, 你正在等待锁的释放, 所以你处于BLOCKED状态

4. WAITING

等待状态. 此时处于等待状态的线程需要等待其他线程的唤醒.
以下三个方法会使线程处于等待状态:

  1. Object.wait():使当前的线程变成等待状态直到另一个线程唤醒它, 会释放锁
  2. Thread.join():等待线程的执行完毕, 底层是Object.wait()方法
  3. LockSupport.park():除非获得调用许可, 非则禁止当前线程进行线程调度

    5. TIMED_WAITING

    超时等待状态. 线程等待一个具体的时间, 到时间后自动被唤醒.

  4. Thread.sleep(long millis):当前的线程睡眠指定的时间, 不会释放锁

  5. Object.wait(long timeout):休眠指定时间, 可以通过notify() notifyAll()唤醒
  6. Thread.join(long millis):等待线程最多执行millis毫秒, 如果为0, 则一直执行直到完毕.
  7. LockSupport.parkNanos(long nanos):除非获得调用许可, 否则禁止当前线程进行线程调度指定时间
  8. LockSupport.parkUtil(long nanos):同上

    6. TERMINATED

    终止状态.线程执行完毕

    线程中断

    Java中没有安全的直接方法来停止线程, 但是提供了线程中断机制来处理需要中断线程的情况, 线程中断是一种协助机制, 中断线程并不能直接中断一个线程, 而是通知线程中断自行处理
    中断的方法:

  9. Thread.interrupt():中断线程, 并不会立即停止线程, 而是这是线程的中断状态为true

  10. Thread.interrupted():测试线程是否被中断. 调用一次设置为中断状态为true, 再次调用使得变为false, 影响线程状态.
  11. Thread.isinterrupted():测试线程是否被中断. 不会影响线程状态

    什么是Java内存模型

在虚拟机中将变量存储到内存, 以及从内存中取出变量这样的底层细节

主内存与工作内存

Java内存模型规定了所有的变量都存在于主内存, 每条线程有自己的工作内存,工作内存保存了线程所用到的变量的内存副本。
image.png

主内存和工作内存的交互模型

内存模型中定义了8种操作完成主内存与工作内存之间具体的交互协议,每个操作都是原子级的, 不能再分

作用于主内存的操作

  1. lock操作
    把一个变量标识为一条线程独占的状态
  2. unlock操作
    把一个处于锁定状态的变量释放出来, 释放后的变量才能被其他的线程锁定
  3. read
    把一个变量的值从主内存传输到工作内存中, 以便load使用
  4. write
    把store操作从工作内存中得到的变量值存入到主内存变量中

作用于工作内存

  1. load
    把read操作从主内存中得倒的变量值放入工作内存的变量副本中
  2. use
    将工作内存中的一个变量的值传递给执行引擎, 当虚拟机遇到一个需要使用的变量的值的字节码执行时会执行这个操作
  3. assign
    将执行引擎接受到的值赋值给工作内存的变量, 当虚拟机遇到一个变量赋值的字节码执行这个操作
  4. store
    将工作内存中的一个变量的值传送到主内存中, 以便随后的write操作

JVM划分内存

image.png

线程私有数据区

程序计数器

三个特点:

  1. 内存较小

当前线程所指向的字节码的行号指示器

  1. 线程私有

多线程是通过线程轮流切换并分配处理器指向时间的方式来实现, 在一个特定的时间,一个处理器只会执行一条线程的指令。每个线程都有一个独立的程序计数器, 互不影响。

  1. 无异常

如果执行的是java方法, 计数器记录的是正在执行的虚拟机字节码指令的地址, 如果是Native方法, 那么计数器为空,此内存区域是唯一一个在java虚拟机规范中没有任何OutOfMemoryError情况的区域

虚拟机栈

  1. 线程私有

与程序计数器一样

  1. 描述java方法执行的内存模型

描述Java方法执行的内存模型, 一个方法对应一个栈帧, 栈帧是方法执行的基础数据结构, 一个方法被调用直至执行完成, 就对应一个栈帧在虚拟机栈从入栈到出栈的过程

  1. 异常

    StackOverflowError
    执行Java方法会执行压栈操作,JVM规定了栈的最大深度, 如果线程执行方法时栈的最大深度大于了规定的深度,那么就会抛此异常

OutOfMemoryError
如果虚拟机扩展时无法申请到足够的内存, 就会抛出此异常

本地方法栈

本地方法栈的作用与虚拟机非常相似

  1. 为native服务

虚拟机栈为Java服务, 本地方法栈为native服务

  1. 异常

StackOverFlowError和OutOfMemoryError

所有线程共享的数据区域

Java堆

Java堆(Java Heap)也就是实例堆,有四个特点

  1. 内存最大

  2. 线程共享

被所有线程共享一块内存的区域, 虚拟机启动时创建

  1. 存放实例

唯一目的就是存放对象的实例,几乎所有的对象实例都在这里分配内存。

  1. GC

Java堆是垃圾收集器管理的主要区域, 因此也被称为“GC堆”

方法区

存储的是已经被虚拟机加载的数据

  1. 线程共享
  2. 存储的数据类型
  • 类的信息
  • 常量
  • 静态变量
  • 即时编译器编译后的代码
  1. 异常

方法区的大小决定JVM 系统可以保存多少个类, 如果定义的类太多, 会报OutOfMemoryError

方法区分为运行常量池和直接内存两部分

  1. 运行常量池

常量池用于存放编译器生成的各种自变量和符号引用, 当类加载之后进入方法区中的运行池常量中存放

  1. 直接内存
  • 不是运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域
  • 直接分配
  • 受设备内存大小的限制
  • 异常

存放:

  • main()
  • 基本类型变量(int,short,long。。。)
  • 引用类型变量(User user,其中user就是引用类型变量)
  • 方法函数

对象访问

  1. Object obj=new Object();

映射如下

  • 使用句柄

image.png
reference中存储的是稳定的句柄地址, 对象被移动(垃圾回收等)时, 只是改变句柄中的实例数据指针, reference不会被修改

  • 使用直接指针

image.png
速度快

垃圾回收—分代收集算法

根据对象存活周期不同将内部划分为以下的区域, 每个区域采用不同的回收算法
image.png
现在大多使用分带收集法, 新生代使用Copying算法, 因为姚回收大部分对象, 复制的较少. 老年代使用标记-整理算法, 每次只回收少量的对象.

枚举类

作用和特点

  1. 用来约束类型
  2. 在拓展性方面比常量更方便

使用

  1. public enum UserRole {
  2. ROLE_ROOT_ADMIN( "系统管理员", 000000 ),
  3. ROLE_ORDER_ADMIN( "订单管理员", 100000 ),
  4. ROLE_NORMAL( "普通用户", 200000 ),
  5. ;
  6. // 以下为自定义属性
  7. private final String roleName; //角色名称
  8. private final Integer roleCode; //角色编码
  9. // 以下为自定义构造函数
  10. UserRole( String roleName, Integer roleCode ) {
  11. this.roleName = roleName;
  12. this.roleCode = roleCode;
  13. }
  14. // 以下为自定义方法
  15. public String getRoleName() {
  16. returnthis.roleName;
  17. }
  18. public Integer getRoleCode() {
  19. returnthis.roleCode;
  20. }
  21. public static Integer getRoleCodeByRoleName( String roleName ) {
  22. for( UserRole enums : UserRole.values() ) {
  23. if( enums.getRoleName().equals( roleName ) ) {
  24. return enums.getRoleCode();
  25. }
  26. }
  27. returnnull;
  28. }
  29. }

泛型

特性

泛型只有在编译阶段有效

  1. List<String> stringArrayList = new ArrayList<String>();
  2. List<Integer> integerArrayList = new ArrayList<Integer>();
  3. Class classStringArrayList = stringArrayList.getClass();
  4. Class classIntegerArrayList = integerArrayList.getClass();
  5. if(classStringArrayList.equals(classIntegerArrayList)){
  6. Log.d("泛型测试","类型相同");
  7. }

编译后, 将泛型的相关信息擦出, 泛型在逻辑上是不同的类型, 但实际是相同的基本类型

泛型的使用

  1. 泛型类
  2. 泛型接口
  3. 泛型方法

泛型类

用在类上

  1. //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
  2. //在实例化泛型类时,必须指定T的具体类型
  3. public class Generic<T>{
  4. //key这个成员变量的类型为T,T的类型由外部指定
  5. private T key;
  6. public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
  7. this.key = key;
  8. }
  9. public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
  10. return key;
  11. }
  12. }
  13. /*
  14. usage
  15. */
  16. //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
  17. //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
  18. Generic<Integer> genericInteger = new Generic<Integer>(123456);
  19. //传入的实参类型需与泛型的类型参数类型相同,即为String.
  20. Generic<String> genericString = new Generic<String>("key_vlaue");
  21. Log.d("泛型测试","key is " + genericInteger.getKey());
  22. Log.d("泛型测试","key is " + genericString.getKey());
  23. //result
  24. 12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456
  25. 12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue

类型参数只能是引用类型, 不能是简单类型

泛型接口

  1. //定义一个泛型接口
  2. public interface Generator<T> {
  3. public T next();
  4. }
  5. /**
  6. * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
  7. * 即:class FruitGenerator<T> implements Generator<T>{
  8. * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
  9. */
  10. class FruitGenerator<T> implements Generator<T>{
  11. @Override
  12. public T next() {
  13. return null;
  14. }
  15. }
  16. /**
  17. * 传入泛型实参时:
  18. * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
  19. * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
  20. * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
  21. * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
  22. */
  23. public class FruitGenerator implements Generator<String> {
  24. private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
  25. @Override
  26. public String next() {
  27. Random rand = new Random();
  28. return fruits[rand.nextInt(3)];
  29. }
  30. }

泛型通配符

Generic不是Generic的父类

所以下面的代码会报错

  1. public void showKeyValue1(Generic<Number> obj){
  2. Log.d("泛型测试","key value is " + obj.getKey());
  3. }
  4. Generic<Integer> gInteger = new Generic<Integer>(123);
  5. Generic<Number> gNumber = new Generic<Number>(456);
  6. showKeyValue(gInteger);
  7. // showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer>
  8. // cannot be applied to Generic<java.lang.Number>

使用通配符

  1. public void showKeyValue1(Generic<?> obj){
  2. Log.d("泛型测试","key value is " + obj.getKey());
  3. }

此处’?’是类型实参,而不是类型形参

泛型方法

  1. public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  2. IllegalAccessException{
  3. T instance = tClass.newInstance();
  4. return instance;
  5. }

/*
泛型方法的基本介绍
@param tClass 传入的泛型实参
@return T 返回值为T类型
说明:
1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
/

  1. public class GenericTest {
  2. //这个类是个泛型类,在上面已经介绍过
  3. public class Generic<T>{
  4. private T key;
  5. public Generic(T key) {
  6. this.key = key;
  7. }
  8. //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
  9. //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
  10. //所以在这个方法中才可以继续使用 T 这个泛型。
  11. public T getKey(){
  12. return key;
  13. }
  14. /**
  15. * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
  16. * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
  17. public E setKey(E key){
  18. this.key = keu
  19. }
  20. */
  21. }
  22. /**
  23. * 这才是一个真正的泛型方法。
  24. * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
  25. * 这个T可以出现在这个泛型方法的任意位置.
  26. * 泛型的数量也可以为任意多个
  27. * 如:public <T,K> K showKeyName(Generic<T> container){
  28. * ...
  29. * }
  30. */
  31. public <T> T showKeyName(Generic<T> container){
  32. System.out.println("container key :" + container.getKey());
  33. //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
  34. T test = container.getKey();
  35. return test;
  36. }
  37. //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
  38. public void showKeyValue1(Generic<Number> obj){
  39. Log.d("泛型测试","key value is " + obj.getKey());
  40. }
  41. //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
  42. //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
  43. public void showKeyValue2(Generic<?> obj){
  44. Log.d("泛型测试","key value is " + obj.getKey());
  45. }
  46. /**
  47. * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
  48. * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
  49. * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
  50. public <T> T showKeyName(Generic<E> container){
  51. ...
  52. }
  53. */
  54. /**
  55. * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
  56. * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
  57. * 所以这也不是一个正确的泛型方法声明。
  58. public void showkey(T genericObj){
  59. }
  60. */
  61. public static void main(String[] args) {
  62. }
  63. }

泛型类钟泛型方法的使用

  1. public class GenericFruit {
  2. //内部类
  3. class Fruit{
  4. @Override
  5. public String toString() {
  6. return "fruit";
  7. }
  8. }
  9. class Apple extends Fruit{
  10. @Override
  11. public String toString() {
  12. return "apple";
  13. }
  14. }
  15. class Person{
  16. @Override
  17. public String toString() {
  18. return "Person";
  19. }
  20. }
  21. class GenerateTest<T>{
  22. public void show_1(T t){
  23. System.out.println(t.toString());
  24. }
  25. //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
  26. //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
  27. public <E> void show_3(E t){
  28. System.out.println(t.toString());
  29. }
  30. //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
  31. public <T> void show_2(T t){
  32. System.out.println(t.toString());
  33. }
  34. }
  35. public static void main(String[] args) {
  36. Apple apple = new Apple();
  37. Person person = new Person();
  38. GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
  39. //apple是Fruit的子类,所以这里可以
  40. generateTest.show_1(apple);
  41. //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
  42. //generateTest.show_1(person);
  43. //使用这两个方法都可以成功
  44. generateTest.show_2(apple);
  45. generateTest.show_2(person);
  46. //使用这两个方法也都可以成功
  47. generateTest.show_3(apple);
  48. generateTest.show_3(person);
  49. }
  50. }

类型方法与可参数

  1. public <T> void printMsg( T... args){
  2. for(T t : args){
  3. Log.d("泛型测试","t is " + t);
  4. }
  5. }
  6. printMsg("111",222,"aaaa","2323.4",55.55);

静态方法与泛型

如果静态方法要使用泛型的话, 那么静态方法必须定义为泛型方法

泛型上下边界

  1. Generic<? extends Number> obj

obj只能是Number或者Number的子类, String会提示错误

  1. Generic<? super Number> obj

obj只能是Number或者Number的父类

I/O

常用的类
Java - 图7

  1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
  2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
  3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
  4. Reader(文件格式操作):抽象类,基于字符的输入操作。
  5. Writer(文件格式操作):抽象类,基于字符的输出操作。
  6. RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

image.png

字节流

文件的拷贝
image.png

利用字节流缓冲区拷贝文件
image.png

使用字节缓冲流拷贝文件
image.png

字符流

逐个字符读取, 写入
image.png
image.png
image.png
使用字符流缓冲区拷贝文件
image.png

使用字符缓冲流拷贝文件
image.png

转换流

将字节流转换为字符流
image.png

RandomAccessFile

不属于流类, 但具有读写文件数据的功能, 可以从文件的任何位置开始并以指定的操作权限读写数据
image.png
image.png

NIO

传统的IO是面向字节流或者字符流的, 而NIO是面向缓冲区的,数据总是聪通道读到缓冲区, 或者聪缓冲区写入到通道中, 其中Selector用于监听多个Channel, 因此单个线程可以监听多个数据通道, 其中包括三个部分

  1. Channel
  2. Buffer
  3. Selector

image.png

NIO的非阻塞

IO的各种流都是阻塞的, 当调用read或者write操作的时候, 线程都是被阻塞. 而NIO的非阻塞模式使一个线程从某通道请求读取数据, 只能得到可用的数据, 如果没有数据, 那么就不会获取, 不会阻塞, 可以做其他的事情, 写也是同样的操作. 所以一个线程可以管理多个输入和输出通道

Channel

跟传统的IO中的Stream差不多是同样的等级, 只不过Stream是单向的, 而Channel是双向的, 可以进行读, 也可以写操作
实现类主要有:

  1. FileChannel
  2. DatagramChannel
  3. SocketChannel
  4. ServerSockketChannel

分别对应 文件IO UDP TCP(Server和client)

Buffer

实际上是一个容器, 是一个连续的数组, Channel提供从文件, 网络读取数据的渠道, 但是文件的读写必须经过Buffer
image.png
Buffer是一个顶层的抽象父类, 他的子类有

  1. ByteBuffer
  2. IntBuffer
  3. CharBuffer
  4. LongBuffer
  5. DoubleBuffer
  6. FloatBuffer
  7. ShortBuffer

    Selector

    是NIO的核心类, Selector可以检测多个注册通道上是否有时间发生. 所以只用一个线程就可以监听多个通道.

    Java数据结构

    image.png

    HashMap

    为什么要重写hashcode和equals方法

利用Hash函数计算存入对象的hashcode, 将对象存入到对应的hashcode索引上, 如果有两个对象的hashcode一样, 那么就产生了”Hash冲突”, Java采用”链地址法”解决方案, 为所有hashcode是i的对象建立一个同义链表, 如果存入一个对对象的时候, 发现此位置已经被占了, 那么就会新建一个链表结点放入存入的对象.
Java - 图23
没有重写两种方法
image.png
第26行会返回null, 因为没有在Key中定义hashcode()方法, 这里调用的是Object类的hashCode()方法, 此类是所有类的父类, 而Object类的hasCode()方法返回hash值是k1对象的内存地址, 假设为1000, 第26行取的是k2对象, 再次调用Object的hashCode()方法, 得到的其实是k2的内存地址, 假设2000, 用于对象不同, 那么得到的地址肯定也不相同, hash值一定不同, 无法取到k2.
Java - 图25

将16行的注释取消, 实现Key的hashCode()方法, 返回的是id的hashcode, k1和k2的id是相同的, 所以得到的hash值是相同的, 但是取到的结果还是null, 是因为没有重写equals方法, 在相同的位置上可能有多个用链表形式存储的对象, 取的时候如果发现有多个对象, 那么就需要调用Key对象的equals方法来判断对象是否相等了, 没有重写equals方法, 所以调用Object的equals方法, 此方法根据两个对象的内存地址判断, 所以k1和k2一定不相等.
Java - 图26

Queue队列

接口于List, Set同一级别, 继承了Collection接口, LinkedList实现了Deque接口

  1. queue.offer() //添加一个元素到末尾,如果满了,返回false
  2. queue.peek() //返回队列的头部元素, 不移除
  3. queue.poll() //返回第一个元素, 并移除

Stack栈

是Vector的一个子类

方法:

  1. boolean empty() //判断是否为空
  2. Object peek() //查看栈顶对象, 不移除
  3. Object pop() //移除栈顶对象, 最为返回值
  4. Object push(Object element)//压入栈
  5. int search(Object element) //搜素元素, 以1为基数

Java8新特性

Lambda表达式

允许把函数作为一个方法的参数,使代码更加的简洁

  1. public class Java8Tester {
  2. public static void main(String args[]){
  3. Java8Tester tester = new Java8Tester();
  4. // 类型声明
  5. MathOperation addition = (int a, int b) -> a + b;
  6. // 不用类型声明
  7. MathOperation subtraction = (a, b) -> a - b;
  8. // 大括号中的返回语句
  9. MathOperation multiplication = (int a, int b) -> { return a * b; };
  10. // 没有大括号及返回语句
  11. MathOperation division = (int a, int b) -> a / b;
  12. System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
  13. System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
  14. System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
  15. System.out.println("10 / 5 = " + tester.operate(10, 5, division));
  16. // 不用括号
  17. GreetingService greetService1 = message ->
  18. System.out.println("Hello " + message);
  19. // 用括号
  20. GreetingService greetService2 = (message) ->
  21. System.out.println("Hello " + message);
  22. greetService1.sayMessage("Runoob");
  23. greetService2.sayMessage("Google");
  24. }
  25. interface MathOperation {
  26. int operation(int a, int b);
  27. }
  28. interface GreetingService {
  29. void sayMessage(String message);
  30. }
  31. private int operate(int a, int b, MathOperation mathOperation){
  32. return mathOperation.operation(a, b);
  33. }
  34. }