Java基础疑难点梳理(泛型到反射9章内容)

文章目录

  • 1. 泛型
  • 2. 基础类库
  • 3. 集合
  • 4. 异常
  • 5. 注解
  • 6. JDBC
  • 7. IO流
  • 8. 网络编程
  • 9. 类加载和反射

1. 泛型

  1. 静态方法的形参,静态变量,静态代码块中不能使用泛型(因为即使泛型不同,还是同一个类,静态变量是属于类的)

  2. instanceof运算符后不能跟泛型类:a instanceof ArrayList

  3. A extends B A[]是B[]的子类 但是List 不是List的子类

  4. 通配符的使用

    public static void main(String[] args) {
            List<Integer> integers = new ArrayList<>();
            //f1(integers); 不能调用 List 不是 List list子类
            f2(integers);//可以使用通配符调用
        }
        public static void f1(List<Object> list){
            for (Object o : list) {
                System.out.println(o);
            }
            list.add("123");
            list.add(123);
        }
        public static void f2(List<?> list){
            for (Object o : list) {
                System.out.println(o);
            }
            //不能添加元素 因为不知道?是什么类型
            //list.add("123"); 如果?是Integer类型呢
           // list.add(123); 如果?是String类型呢
            //只能添加null
            list.add(null);//可以添加null 因为null是所有类的子类
        }
    
    
    • ? 普通通配符:可以匹配所有的泛型,不能添加元素(除了null)
    • ?extends A 上限通配符:可以匹配A以及A的子类的泛型,不能添加元素(除了null)
    • ?super A 下限通配符:可以匹配A以及A的父类的泛型,可以添加A类型对象和null
    class A{
    
    }
    class A1 extends A{
    
    }
    class A2 extends A{
    
    }
     public static void main(String[] args) {
           List<A> list1=new ArrayList<>();
           f1(list1);//ok
           f2(list1);//ok
           f3(list1);//ok
    
            List<A2> list2=new ArrayList<>();
            f1(list2);//ok
            f2(list2);//ok
            f3(list2);//not ok A2不是A1的子类
        }
    //f1 f2 f3不构成参数重载 函数名必须不同
        public static void f1(List<?> list){
    
        }
        public static void f2(List<? extends A> list){
    
        }
        public static void f3(List<? super A1> list){
    
        }
    
  5. 上下限的设置不仅仅可以配合?一起使用,也可以单独作为类的泛型界限

    class A <T extends Number>{
        T a;
    }
    
  6. 关于泛型类(接口的继承)

    class A <T>{
    }
    class A1 extends A<String>{
    }
    class A2<T> extends A{//A2是子类中的新的泛型 与A无关 A中的泛型默认是Object
    }
    class A3<T> extends A<T>{//A2中的T和A相同  继承了T
    }
    class A4<T,E> extends A<T>{//继承了T并且拓展了泛型E
    }
    //class A5 extends A{
    //}
    //class A6 extends A{
    //}
    
  7. 泛型方法

     	public static void main(String[] args) {
            Integer f = f(new ArrayList<Integer>(), 12);
            Double f1 = f(new ArrayList<String>(), 1.2);
        }
        public static <T,E>  E f(List<T> list,E value){
            return  value;
        }
    	//泛型方法中的形参也可以和?一起使用
     	public static <T> void f2(List<? extends T> list){
            
        }
    
    • 泛型方法一般可以替代通配符?
    • 泛型方法一般用于参数之间具有依赖性或者参数与返回值之间有依赖性的情况
    public static void main(String[] args) {
            ArrayList<Integer> src = new ArrayList<>();
            src.add(1);
            src.add(2);
            ArrayList<Integer> dest = new ArrayList<>();
            copy(src,dest);
        }
    //参数互相依赖
    public static <T>  void copy(List<T> src,List<T> dest){
        for (int i = 0; i < src.size(); i++) {
            dest.add(src.get(i));
        }
    }
    public static  void f2(List<?> src,List<?> dest){
        for (int i = 0; i < src.size(); i++) {
           // dest.add(src.get(i)); dest的类型是? 只能往里面添加null
        }
    }
    //参数与返回值依赖
    public static <T>  T f(List<T> list){
        return list.get(0);
    }
    
  8. 构造器泛型和菱形语法失效

    class A {
    public <T> A(T x){
        System.out.println(x);
    }
    }
    public static void main(String[] args) {
     //构造器泛型
        new A(123);
        new A("1.23");
    }
    
    class A<E> {
        public <T> A(T x){
            System.out.println(x);
        }
    }
     public static void main(String[] args) {
            A<String> a1=new A<>(123);
           // A a2=new A(123); 显示指定了泛型构造器中的形参类型
     }
    
  9. 泛型擦除

    1. 具有泛型信息的对象赋值给一个没有泛型信息的的变量时会发生泛型擦除
    public static void main(String[] args) {
            List<Integer> list1=new ArrayList<>();
        	list1.add("123");//not ok
            List list2=list1;//泛型擦除 
        	list2.add("123");// ok
            List list3=new ArrayList();//不带泛型的对象赋值给一个带泛型的变量-->"堆污染"
            List<Integer> list4=list3;//list4只能添加Integer元素
        }
    
  10. 没有泛型数组

    • 泛型信息只在编译期间有,在运行期间是没有的
    • 不管是List List List在运行期间都是List
    //java数组存在型变的问题:A是B的子类 A[]也是B[]的子类 可以向上转型
    Integer[] a=new Integer[2];
    Object[] b=a;
    b[0]="hello";//编译ok 运行期间错误
    
    //由于上面的型变问题,假设可以创建泛型数组
    List<Integer>[] a=new ArrayList<Integer>[2];//编译ok(假设可以创建泛型数组)
    Object[] b=a;//ok
    a[1]=new ArrayList<String>();//运行期间错误 
    //所以不能创建泛型数组
    
    //此外
    T a=new T[2];//这种方式也不行
    //数组的创建在运行期间,泛型信息在运行期间没有,所以不知道创建什么类型的数组
    
    //ps:这样的创建数组是可以的 即不给ArrayList添加泛型
    List<Integer>[] a=new ArrayList[2];
    a[0]=new ArrayList<Integer>();
    a[1]=new ArrayList<Integer>();
    //a[1]=new ArrayList(); not ok
    a[1].add("123");//not ok 引用类型的泛型限制
    a[1].add(123);//ok
    //或者
    List[] a=new ArrayList[2];
    a[0]=new ArrayList<Integer>();
    a[0].add("123");//上面的Integer泛型失效 因为引用类型的泛型是Object 所以可以添加"123"
    a[0].add(123);
    a[1]=new ArrayList<String>();
    a[1].add("123");//ok
    a[1].add(123);//ok
    

  11. 2. 基础类库

    1. Scanner可以读键盘输入、也可以读取文件

      • next以空格为分隔符读取
      • nextLine以换行符为分隔符读取
      • 可以指定分隔符:scanner.useDelimiter("\n");
      • 读键盘:Scanner scanner=new Scanner(System.in);
      • 读文件:Scanner scanner = new Scanner(new File("data.txt"));
    2. System类

       public static void main(String[] args) throws FileNotFoundException {
              /**
               * getenv:获取系统变量
               * getProperties: 获取系统属性值
               */
              System.out.println(System.getenv("JAVA_HOME"));
              System.out.println(System.getProperty("os.name"));
              Map<String, String> env = System.getenv();//获取所有环境变量
              for (String s : env.keySet()) {
                  System.out.println(s+"--->"+env.get(s));
              }
              Properties properties = System.getProperties();//获取系统所有的属性值
              System.out.println(properties);
      
              /**
               * 获取毫秒和纳秒时间戳
               */
              System.out.println(System.currentTimeMillis());//获取当前时间戳ms
              System.out.println(System.nanoTime());//获取当前时间戳ns
      
              /**
               * identityHashCode:唯一标识对象
               * 值相同则说明两个对象的地址相同 是同一个对象
               */
              Object o1 = new Object();
              Object o2 = new Object();
              System.out.println(System.identityHashCode(o1));//460141958
              System.out.println(System.identityHashCode(o2));//460141958
      
              String s1=new String("hello");
              String s2=new String("hello");
              System.out.println(System.identityHashCode(s1));//460141958
              System.out.println(System.identityHashCode(s2));//356573597
      
              String s3="hello";
              String s4="hello";
              System.out.println(System.identityHashCode(s3));//1735600054
              System.out.println(System.identityHashCode(s4));//1735600054
          }
      
    3. Runtime类

       public static void main(String[] args) throws IOException {
              Runtime runtime = Runtime.getRuntime();
              System.out.println(runtime.availableProcessors());//可用cpu核数
              System.out.println(runtime.freeMemory());//空闲jvm内存 单位字节
              System.out.println(runtime.maxMemory());//最大jvm内存
              runtime.exec("notepad.exe");//打开记事本
          }
      
    4. Object类

      • boolean equals(Object obj)
      • protected void finalize()
      • Class getClass()
      • int hashCode()
      • String toString()
      • wait()
      • notify()
      • notifyAll()
      • protected native clone():本地方法,自定义的类可以实现Cloneable接口实现克隆(对于引用类型是浅拷贝)
      class Person implements Cloneable{
          String name;
          Pet pet;
          @Override
          protected Person clone() throws CloneNotSupportedException {
              return (Person) super.clone();
          }
      
          public Person(String name, Pet pet) {
              this.name = name;
              this.pet = pet;
          }
      }
      class Pet{
          String name;
          public Pet(String name) {
              this.name = name;
          }
      }
       public static void main(String[] args) throws IOException, CloneNotSupportedException {
      
              Person p1 = new Person("tom",new Pet("dog"));
              Person p2=p1.clone();
              /**
               * clone是浅拷贝
               * Pet这个引用类型的变量只是拷贝了引用
               */
              System.out.println(p1==p2);//false
              System.out.println(p1.pet==p2.pet);//true
          }
      
      //如何修改为深拷贝?
      //1. 给Pet类也实现 clone()方法
      @Override
      protected Pet clone() throws CloneNotSupportedException {
          return (Pet) super.clone();
      }
      //2. 修改Person类的clone方法如下
       @Override
      protected Person clone() throws CloneNotSupportedException {
          Person p=(Person) super.clone();
          p.pet=pet.clone();
          return p;
      }
      

    3. 集合

    1. 集合转化为数组

      Integer[] arr= new Integer[list.size()];//不能是int[] 必须是Integer[]
      arr=list.toArray(arr);
      System.out.println(Arrays.toString(arr));
      
      Object[] arr2=list.toArray();//这种方式只能返回Object[]类型的数组
      System.out.println(Arrays.toString(arr2));
      
    2. 集合框架

      1. Collection接口

        • List
          • ArrayList
          • LinkedList
        • Set
          • HashSet(无序指的是元素按照hashCode值排列的,输出时的序列是固定的)
            • LinkedHashSet(有序指的是按照元素输出是按添加顺序排列的)
          • SortedSet
            • TreeSet(判断对象是否相同是根据compareTo方法,而不是hashCode方法)(不能添加null,因为需要排序)
        • Queue
          • PriorityQueue(不能添加null,因为需要排序)
          • ArrayDeque(常用做栈)
      2. Map接口

        • HashMap
          • LinkedHashMap
        • SortedMap
          • TreeMap(不能添加null,因为需要排序)
      3. Lambda表达式遍历集合

        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
               System.out.print(integer);//lambda的本质:匿名内部类
            }
        });
        list.forEach(x->System.out.println(x));//可以添加{}进行其他操作
        list.forEach(System.out::println);//只能单纯打印 比较方便
        
        
      4. 使用Iterator遍历集合

        • 在使用增强for循环遍历集合时是不能进行删除操作的
        • 可以使用iterator遍历集合使用iterator.remove()方法删除集合中的元素,不能使用集合自身的remove方法
        • 在使用iterator遍历集合修改元素对原集合没有影响
      5. HashSet(HashMap)添加元素过程

        • 先根据hashCode方法计算出hash值找到对应的槽
        • 如果该槽位没有元素,直接添加;如果有元素了,则沿着该槽位上的链表遍历,如果通过equals比较得到一个true,则添加失败;如果遍历完了该链表还没有equals成功,则在末尾添加上该元素
        • 一般情况下,hashCode相同equals不一定相同;但是equals相同hashCode应该也相同
        • ps: 不要修改已经添加进set中的元素的变量(参与hashCode equals计算)值
        class A{
            int a;
            @Override
            public int hashCode() {
                return this.a;
            }
            @Override
            public boolean equals(Object obj) {
                A x=(A)obj;
                return this.a==x.a;
            }
            public A(int a) {
                this.a = a;
            }
        }
        public static void main(String[] args) {
                A x= new A(123);
                HashSet<A> set=new HashSet<>();
                set.add(x);
                System.out.println(set.size());
                x.a=456;
                System.out.println(set.remove(new A(456)));//false 删除失败 因为x在set中的位置是123 位置456是空着的
                set.add(new A(123));//添加成功 该对象会落到123位置 该位置已经有x了 然后使用equals比较 由于x的a属性值被修改了 比较失败 equals返回false 因此添加成功
                System.out.println(set.size()); // 2
        }
        
      6. TreeSet注意事项

        1. 默认升序,可以使用使用定制排序(Comparable)和自然排序(Comparable)自定义排序规则
        TreeSet<Integer> treeSet=new TreeSet<>();
        treeSet.add(3);
        treeSet.add(1);
        treeSet.add(2);
        System.out.println(treeSet);//1 2 3
        TreeSet<Integer> treeSet2=new TreeSet<>((a,b)->b-a);//lambda简化了new Comparator
        treeSet2.add(3);
        treeSet2.add(1);
        treeSet2.add(2);
        System.out.println(treeSet2);//3 2 1
        
        1. 对自定义的对象如果相应使用TreeSet必须实现Comparable接口或者传入Comparator
        2. 添加进TreeSet中的对象,如果再修改其属性值,不会触发二次排序
        3. 如果compareTo(compare)方法总是返回1,则每次都能添加成功,有可能treeset中有多个元素引用,但是这些引用都是指向同一个对象的
        4. equals返回true时,compareTo(compare)方法应该返回0
      7. List接口注意事项

        1. 删除元素remove(Object obj)时是从头开始调用equals比较的,如果重写的equals一直返回true,则每次都会删除第一个元素
        2. 同理,由于List是根据equals方法比较元素,如果重写的equals一直返回true,indexOf(obj)也会一直返回0
        class A{
            int num;
            public A(int num) {
                this.num = num;
            }
        
            @Override
            public boolean equals(Object obj) {
                return true;
            }
        }
        public static void main(String[] args) {
                List<A> list=new ArrayList<>();
                A a1 = new A(123);
                A a2 = new A(456);
                list.add(a1);
                list.add(a2);
                System.out.println(list.indexOf(a2));//0
                list.remove(a2);//看似删除a2实际上把a1删除了
                System.out.println(list.get(0).num);//456
            }
        
      8. Arrays工具类

        1. 可以使用asList方法创建一个List,但是不能删除,也不能添加
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
        System.out.println(numbers);
        System.out.println(numbers.getClass());//class java.util.Arrays$ArrayList
        numbers.add(1);//报错
        numbers.remove(1);//报错
        
        1. 常用方法还有:
          • public static void sort(int[] a)
          • public static int binarySearch(int[] a, int key)
          • public static void fill(int[] a, int val)
          • public static int[] copyOf(int[] original, int newLength)
      9. HashMap

        • 判断两个键是否相等:hashCode相同并且equals方法返回true
        • 判断两个值是否相同:equals方法返回true即可
      10. Properties的使用

         public static void main(String[] args) throws IOException {
                Properties properties = new Properties();
                properties.setProperty("name","root");
                properties.setProperty("passwd","123");
                //写到文件中
                properties.store(new FileOutputStream("info.properties"),"user info");
                Properties properties1 = new Properties();
        
                //从properties文件中读取
                properties1.load(new FileInputStream("info.properties"));
                System.out.println(properties1);//{passwd=123, name=root}
                System.out.println(properties1.getProperty("name"));//root
            }
        
      11. WeakHashMap

        • HashMap中的key是强引用
        • WeakHashMap中的key是弱引用,若除了key之外没有其他引用引用对象,则该对象会被gc回收
         public static void main(String[] args) throws IOException {
                WeakHashMap<Object, String> weakHashMap = new WeakHashMap<>();
                weakHashMap.put(new Object(), "ob1");
                weakHashMap.put(new Object(), "ob2");
                weakHashMap.put(new Object(), "ob3");
                System.out.println(weakHashMap.size());//3
                System.gc();
                System.runFinalization();
                System.out.println(weakHashMap.size());//0
            }
        //map中的key实际上是对象的引用 当发生gc后 三个obj对象被回收 之后weakHashMap会删除这三个对象对应的引用变量key-value对
        
      12. Collections工具类拥有的功能

        • 逆序 随机排序 排序 交换元素位置
        • 二分查找 max min fill 求某个元素出现的次数 替换
        • 包装原来的Collection接口实现类得到一个线程安全的实现类(本质上就是返回一个新的Collection实现类,该实现类的set get等操作加上了synchronized)

    4. 异常

    1. 无论使没使用try…catch,JVM都会产生一个异常对象,如果处理了,程序正常退出(Process finished with exit code 0),否则程序异常退出(Process finished with exit code 1)

    2. try后面的catch捕获的范围应该从小到大,从大到小会编译异常,cathch块只会执行一个

    3. try后面至少需要一个catch块或者finally块

    4. 一个catch块中可以有多个异常

      try {
          int a = 1 / 0;
      } catch (ArithmeticException | IndexOutOfBoundsException e) {
          e.printStackTrace();
      }
      
    5. Throable(分类和继承体系)

      1. Error
        • IOError
        • LinkageError
        • ThreadDeath
      2. Exception
        • RuntimeException
          • IndexOfBoundsException
          • NullPointerException
          • ClassCastException
        • IOException
    6. fianlly块

      • 常用来回收关闭资源
      • 不管有没有发生异常,fianlly块一定会执行
      • 即使try中使用了return语句,fianlly块一定会执行
      private static void f() {
          try {
              int a = 1 / 1;
              return;
          } catch (ArithmeticException e) {
              e.printStackTrace();
          }finally {
              System.out.println("finally");
          }
      }
      
      • finally返回值的几种情况
      /**
       * finally块中对a的累加赋值不会影响最终的返回值1
       * 因为在return a时a的值被保存了一份到栈中
       * @return 1
       */
      private static int f() {
          int a=1;
          try {
              return a;
          } catch (ArithmeticException e) {
              e.printStackTrace();
          }finally {
              a=5;
              a++;
          }
          return -1;
      }
      /**
       * 在finally块中直接返回了
       * @return 6
       */
      private static int f() {
          int a=1;
          try {
              return a;
          } catch (ArithmeticException e) {
              e.printStackTrace();
          }finally {
              a=5;
              a++;
              return a;
          }
      }
      
      • 但是如果在try块中调用了System.exit(1)等方法,finally块是不会执行的
    7. 运行时异常和编译时异常

      • 运行时异常:比如数组越界、除0异常,在编译期间是不能发现的
      • 编译时异常:比如IO读取,不使用try…catch进行处理编译是不能通过的
    8. Throws和Throw

      • Throws: 在函数签名后添加,表示当前函数不知道怎么处理异常,交给调用者处理,逐层往上,如果main函数也没有处理,则JVM会报错并打印错误信息
      • Throw: 一般用在catch或者finally块中,指定异常类型抛出,并给出自定义的异常信息, throw出类什么异常,方法签名应该throws出该异常或者该异常的父类
      try {
             int i=1/0;
         }catch (ArithmeticException e){
             throw new RuntimeException("除数不能为0");
         }
      
    9. 子类重写父类的方法时,子类重写的方法的异常<=父类中的异常

    10. 自定义异常类

      class MyException extends Exception{
          public MyException() {
          }
          public MyException(String message) {
              super(message);
              System.out.println("自定义异常");
          }
      }
      try {
         int i=1/0;
      }catch (ArithmeticException e){
         throw new MyException("除数不能为0");
      }
      

    5. 注解

    1. 自定义注解使用@interface, 注解中的成员变量使用无参方法声明

      public @interface MyTag {
          String name() default "tom";
          int age() default 18;
      }
      public class Demo1 {
          @MyTag
         public void f(){
      
         }
      }
      
    2. 注解不会自己生效,需要开发者处理

      @Retention(value = RetentionPolicy.RUNTIME)//声明注解可以保留到运行时
      public @interface MyTag {
          String name() default "tom";
          int age() default 18;
      }
      class A{
          public void f(){}
      }
      class A1 extends A{
      
          @MyTag(name = "bob",age = 19)
          @Override
          public void f() {
              super.f();
          }
      }
       public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
              Annotation[] annotations = Class.forName("annotation.A1").getMethod("f").getAnnotations();
              System.out.println(annotations.length);//1 @Override注解在运行时获取不到
              for (Annotation annotation : annotations) {
                  if(annotation instanceof MyTag){
                      System.out.println("tag.name="+((MyTag) annotation).name());
                      System.out.println("tag.age="+((MyTag) annotation).age());
                  }
              }
          }
      
    3. 3种注解范围

      1. @Retention(RetentionPolicy.SOURCE)
      2. @Retention(RetentionPolicy.CLASS): 默认,不能通过发射获取
      3. @Retention(RetentionPolicy.RUNTIME),保留到运行时,可以通过反射获取
    4. @Target(value = ElementType.FIELD)用来指明注解作用范围,作用于类、方法、属性…

    5. 自定义一个类似于Junit中的@Test注解

      public class Demo1 {
          @MyTest
          public void f1(){
              System.out.println("f1");
          }
          @MyTest
          public void f2(){
              System.out.println("f2");
          }
          @MyTest
          public void f3(){
              System.out.println("f3");
          }
          @MyTest
          public void f4(){
              System.out.println("f4");
          }
          public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
              Method[] methods = Class.forName("annotation.Demo1").getMethods();
              Demo1 obj = new Demo1();
              for (Method method : methods) {
                  if(method.isAnnotationPresent(MyTest.class)){
                      method.invoke(obj);
                  }
              }
          }
      }
      
      @Retention(value = RetentionPolicy.RUNTIME)
      @Target(value = ElementType.METHOD)
      public @interface MyTest {
      
      }
      

    6. JDBC

    1. JBDC提供了一套接口,底层实现可以是mysql的驱动jar包,也可以是oracle的驱动jar包(这些jar包由数据库产商提供实现)

    2. 数据库语言分类

      • DML: 数据操作语言,比如update insert deletw
      • DDL: 数据库定义语言,比如create alter drop truncate
      • DCL: 数据控制语言,grant revoke
      • 查询语句:select
    3. JDBC常用API

      • DriverManager
      • Connection
      • Statement
      • PreparedStatement
    4. JDBC编程步骤

      1. 加载数据库驱动Class.forName("com.mysql.jdbc.Driver"),最新的数据库驱动不需要这一步

    Java基础疑难点梳理(泛型到反射9章内容)_第1张图片

    1. 通过DriverManager获取Connection
    2. 通过Connnection创建Statement对象(基本的Statement preparedStatement prepareCall)
    3. 通过Statement执行SQL语句
      • boolean execute: 可以执行任何SQL语句
      • int executeUpdate: 执行DML DDL
      • ResultSet executeQuery: 查询
    public static void main(String[] args) throws ClassNotFoundException {
            Class.forName("com.mysql.cj.jdbc.Driver");//加载数据库驱动 高版本mysql.jar包可以省略这一步
            String url="jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=Asia/Shanghai";
            String userName="root";
            String password="123";
            try (//自动关闭资源的try用法
                //获取数据库连接
                Connection connection= DriverManager.getConnection(url,userName,password);
                Statement statement = connection.createStatement();
                ResultSet rs = statement.executeQuery("select  * from student");
            ){
                while(rs.next()){
                    System.out.println("stuNo: "+rs.getInt("stuno")+" name: "+rs.getString("name"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
    1. execute

      • 可以执行几乎所有的sql语句
      • 只返回boolean值,表示是否返回了Result对象
      • 使用getResult方法获得Result对象,使用getUpdateCount获得DML语句影响的记录条数(是查询语句的话getUpdateCount返回-1)
      Connection connection= DriverManager.getConnection(url,userName,password);
      Statement statement = connection.createStatement();
      boolean hasResult = statement.execute("select  * from student");
      if(hasResult){
          ResultSet resultSet = statement.getResultSet();
          while(resultSet.next()){
              System.out.println(resultSet.getInt("stuno")+"  "+resultSet.getString("name"));
              System.out.println(resultSet.getInt(1)+"  "+resultSet.getString(2));
          }
      }
      
    2. PreparedStatement

      • 适用于被反复被执行的一条相似的sql语句,比如批量添加数据
      Connection connection= DriverManager.getConnection(url,userName,password);
      //由于主键自增 所以第一个参数可以设置为null
      PreparedStatement preparedStatement = connection.prepareStatement("insert into student values(null,?) ");
       for (int i = 0; i < 10; i++) {
          preparedStatement.setString(1,"Tim"+i);
          preparedStatement.execute();
      }
      
      • 使用?占位符代替拼接的参数,普通的执行语句当参数时字符串时需要使用单引号引起来,这样容易导致Sql注入,所以PreparedStatement也可以防止sql注入
      • 优点总结:
        • 预编译,性能好
        • 无需拼接sql语句,编程简单
        • 防止sql注入
    3. Blob: 存储二进制数据到数据库(图片 视频等)

      //存储图片数据
      Connection connection= DriverManager.getConnection(url,userName,password);
      PreparedStatement preparedStatement = connection.prepareStatement("insert into student values(null,?,?) ");
      for (int i = 0; i < 5; i++) {
          FileInputStream fileInputStream = new FileInputStream("1.jpg");
          preparedStatement.setString(1,"Tim"+i);
          preparedStatement.setBinaryStream(2,fileInputStream,fileInputStream.available());
          preparedStatement.execute();
      }
      
      //读取图片数据
       ResultSet resultSet = statement.executeQuery("select * from student");
          while (resultSet.next()){
             String name=resultSet.getString(2);
             Blob blob = resultSet.getBlob(3);//数据库表第3列村存储的是图片
             FileOutputStream out = new FileOutputStream(name+".jpg");
             out.write(blob.getBytes(1, (int) blob.length()));
      }
      
    4. JDBC默认事务自动提交

      1. connection.setAutoCommit(false);: 开启事务,需要手动提交
      2. connection.commit();: 手动提交事务
      3. connection.rollback();: 回滚事务
    5. 批量操作

      Statement statement = connection.createStatement();
      //只能添加DDL DML语句 不能执行查询语句
      statement.addBatch("update student set name='tom111' where stuno=1");
      statement.addBatch("update student set name='tom222' where stuno=2");
      statement.addBatch("update student set name='tom333' where stuno=3");
      int[] ret = statement.executeBatch();//每个sql语句执行结果的返回值
      

    7. IO流

    1. File类

      • 只能新建、删除、重命名文件和目录,不能访问文件内容本身
      • 如要访问文件内容本身,需要IO流
      • 常用方法分类
        • 访问文件名相关操作:获取文件(目录)相对路径,获取文件(目录)绝对路径,获取上一级目录
        • 文件检查相关方法:判断文件(目录)是否存在、判断是否可写(可读)、判断是否是文件(目录)
        • 获取文件信息:获得修改时间、获得文件内容长度
        • 文件操作相关方法:创建新的文件、删除文件、创建临时文件
        • 目录操作相关方法:创建目录、列数目录下的内容
    2. 输入/输出流

      • 输入流:只能从中读数据
      • 输出流:只能往里写数据
      • 从内存的角度,往磁盘写数据是输出流,从磁盘读数据是输入流
      • 从网络编程的服务器角度,发送数据给客户端是输出流,客户端接受数据时输入流
    3. 流的分类

      1. 输入流
        1. InputStream
        2. Reader
      2. 输出流
        1. OutputStream
        2. Writer
    4. InputStream和Reader(两个都是抽象类)

      1. InputStream中的方法
        • int read(): 返回字节数据
        • int read(byte[] b):返回读取的实际字节数
        • int readByte(byte[] b,int off,int len):返回读取的实际字节数
      2. Reader中的方法
        • int read(): 返回字符数据
        • int read(char[] buf):返回读取的实际字符数
        • int readByte(char[] buf,int off,int len):返回读取的实际字符数
    5. FileInputStream和FileReader

      /**
      * FileInputStream
      * 逐个字节读取 当有汉字时会出现乱码
      */
      FileInputStream inputStream = new FileInputStream("a.txt");
      int b=0;
      while((b=inputStream.read())!=-1){
          System.out.println((char)b);
      }
      
      /**
       * FileInputStream
       * 使用字节数组读取
       * 也会出现中文乱码
       * 假设文件内容:中国
       * 字节数组b为3时可以读取正确(utf-8汉字3个字节编码)
       * 字节数组b为4时就会乱码
       */
      FileInputStream inputStream = new FileInputStream("a.txt");
      byte[] b=new byte[3];
      int len;
      while((len=inputStream.read(b))!=-1){
          System.out.println(new String(b,0,len));
      }
      
       /**
       * FileReader
       * 逐个字符读取 不会发生中文乱码
       */
      FileReader reader = new FileReader("a.txt");
      int c=-1;
      while((c= reader.read())>0){
          System.out.println((char)c);
      }
      /**
      * FileReader
      * 用字符数组读取
      */
      FileReader reader = new FileReader("a.txt");
      char[] buf=new char[4];
      int len=-1;
      while((len= reader.read(buf))>0){
          System.out.println(new String(buf,0,len));
      }
      
    6. 关于Unicode字符集和编码格式的一些思考

      Unicode给各国语言字符都设置了一个编号,比如汉字’中’的编号是20013,而UTF-8和GBK是编码格式,举个例子,8可以怎么表示,可以是0001000一个字节表示,也可以是0000000,0001000两个字节表示,不管你怎么表示,只要能最终根据表示方式还原成8即可;因此不管使用utf-8编码也好、gbk编码也好,只是把字符用不同的形式编码而已,码点值都是一样的,那么在解码时就可以安装相同的编码方式解码,得到正确的码点值,这样就不会产生乱码

      String s="你好";
      System.out.println(s.getBytes("utf-8").length);//6 u8采用3个字节表示一个汉字
      System.out.println(s.getBytes("gbk").length);//4 gbk采用2个字节表示一个汉字
      
      //将一个utf-8编码的字符串转化成gbk编码的字符串
      String s_utf8="你好";
      byte[] bytes = s_utf8.getBytes("gbk");//先以gbk方式获取字节
      String s_gbk=new String(bytes,"gbk");//再以gbk方式创建字符串
      System.out.println(s_gbk);
      
      //所以字节是通用的 这也就是为什么后面有转化流的出现
      //假如一个文件的编码格式是gbk 平台系统的编码方式是utf-8 则可以先使用字节流以gbk的方式读取,然后转化成utf-8格式的字符输入流
      //假如当前平台系统的编码方式是utf-8,需要将数据以gbk格式存储,则可以将字符数据以utf-8方式获取字节输出流,然后再转化成gbk格式的字符输出流
      
    7. OutputStream和Writer

      1. OutputStream中的方法

        • void write(int c)
        • void write(byte[] b)
        • void write(byte[] b,int off,int len)
      2. Writer中的方法

        • void write(char c)
        • void write(char[] buf)
        • void write(char[] buf,int off,int len)
        • void write(String str)
        • void write(String str,int off,int len)
      3. FileOutputStream和Writer

        FileOutputStream outputStream = new FileOutputStream("out.txt");
        int b=67;
        outputStream.write(b);
        byte[] bytes={'a','b','c'};
        outputStream.write(bytes);
        outputStream.write(bytes,1,2);
        outputStream.flush();
        outputStream.close();
        //文件内容:Cabcbc
        
        FileWriter writer = new FileWriter("out.txt");
        char c='a';
        char[] buf={'b','c','d'};
        String str="efg";
        writer.write(c);
        writer.write(buf);
        writer.write(buf,1,2);
        writer.write(str);
        writer.write(str,1,2);
        writer.flush();//不要忘记刷新
        writer.close();//不要忘记关闭流 一般关闭时会自动刷新
        
      4. 处理流(PrinsStream)用来包装节点流(FileInputStream FileOutputStream FileReader FileWriter)

        //处理流1PrintStream
        FileOutputStream outputStream = new FileOutputStream("out.txt");
        PrintStream printStream = new PrintStream(outputStream);
        printStream.print("123");//没有换行符
        printStream.println("hello world");//自动写入换行符
        printStream.print("456");//没有换行符
        printStream.close();//可以只关闭最上层处理流 被包装的节点流会被自动关闭
        outputStream.close();
        
      5. 转化流

        1. InputStreamReader: 字符输入流->字节输入流

        2. OututStreamReader: 字符输出流->字符输出流

        3. ps: 没有字符流->字节流,因为没有必要,一是字符流使用起来更方便,二是字节流可以转化为字符流

        4. InputStreamReader使用场景:

          //out.txt是gbk编码的 内容:中国是一个伟大的国家!
          FileReader reader = new FileReader("out.txt");
          char[] buf=new char[1024];
          int len=-1;
          while((len=reader.read(buf))>0){
              System.out.println(new String(buf,0,len));
          }
          //输出:�й���һ��ΰ��Ĺ���!  因为机器是utf-8去解码的 所以乱码了
          
          //解决:使用InputStreamReader转化
          FileInputStream inputStream = new FileInputStream("out.txt");//字节读取是不管文件编码方式的
          InputStreamReader reader = new InputStreamReader(inputStream,"gbk");//用gbk方式解码得到字符输出流
          char[] buf=new char[1024];
          int len=-1;
          while((len=reader.read(buf))>0){
              System.out.println(new String(buf,0,len));
          }
          
        5. OutputStreamReader使用场景:

          //当前系统编码是utf-8 需要将"中国是一个伟大的国家!"以gbk的编码格式写出
          FileOutputStream outputStream = new FileOutputStream("out.txt");
          OutputStreamWriter writer = new OutputStreamWriter(outputStream,"gbk");
          writer.write("中国是一个伟大的国家!");
          writer.close();
          
      6. 重定向输出

        FileOutputStream outputStream = new FileOutputStream("out.txt");
        PrintStream printStream = new PrintStream(outputStream);
        System.setOut(printStream);
        System.out.println("hello world");
        System.out.println("你好 世界");//本来应该打印到控制台 现在重定向到了文件中
        printStream.close();
        
      7. 重定向输入

        FileInputStream inputStream = new FileInputStream("out.txt");
        System.setIn(inputStream);
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {//无需键盘输入 直接从out.txt文件中读取数据
            System.out.println("键盘输入的内容: "+scanner.nextLine());
        }
        
      8. 对象流以及序列化反序列化

        //序列化
        FileOutputStream outputStream = new FileOutputStream("obj.txt");
        ObjectOutputStream stream = new ObjectOutputStream(outputStream);
        Person p = new Person("tom", 18);
        stream.writeObject(p);
        stream.close();
        
        //反序列化
        FileInputStream inputStream = new FileInputStream("obj.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Person p = (Person) objectInputStream.readObject();
        System.out.println("name: "+p.name+" age: "+p.age);
        objectInputStream.close();
        
        //序列化反序列化的对象需要实现Serializable(空接口)
        class Person implements Serializable{
            public String name;
            public int age;
        
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
        }
        //如果序列化之后又修改了Person类就会反序列化失败
        //添加序列化号的作用:
        //private static final long serialVersionUID = 1; 名字必须是serialVersionUID
        //序列号不变,即使修改了Person类也可以反序列化
        //序列号改变,即使没有修改Person类反序列化也会失败
        //总之,序列化和反序列化时的serialVersionUID应该是一样的 否则会反序列化失败
        
      • 反序列化时不会调用构造器创建对象

      • 对某个成员属性如果不想被序列化,可以使用transient修饰

      • 静态变量不会被序列化

      • 带有引用属性的序列化

        //序列化
        FileOutputStream outputStream = new FileOutputStream("obj.txt");
        ObjectOutputStream stream = new ObjectOutputStream(outputStream);
        Pet dog = new Pet("dog");
        Person p1 = new Person("tom", dog);
        stream.writeObject(p1);
        Person p2 = new Person("bob", dog);//同一个对象只会被序列化一次  这里的dog只是一个引用编号
        //每个序列化后的对象在磁盘中都有1个编号
        stream.writeObject(p2);
        
        //反序列化
        FileInputStream inputStream = new FileInputStream("obj.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Person p1 = (Person) objectInputStream.readObject();
        Person p2 = (Person) objectInputStream.readObject();
        System.out.println(p1.pet==p2.pet);//true  
        objectInputStream.close();
        
        //两个类都要实现序列化接口
        class Person implements Serializable{
            String name;
            Pet pet;
        
            public Person(String name, Pet pet) {
                this.name = name;
                this.pet = pet;
            }
        }
        class Pet implements Serializable{
            String name;
        
            public Pet(String name) {
                this.name = name;
            }
        }
        
        //多次写入同一个对象时 第二次以及后面写入时如果修改了对象的属性值是无效的
        Pet dog = new Pet("dog");
        Person p1 = new Person("tom", dog);
        stream.writeObject(p1);
        dog.name="small dog";//这里虽然修改了name 但是写入的还是原来的"dog"
        Person p2 = new Person("bob", dog);
        stream.writeObject(p2);
        
      • 对象类不仅可以写对象数据,基本类型的包装类型也可以写入

        FileOutputStream outputStream = new FileOutputStream("obj.txt");
        ObjectOutputStream stream = new ObjectOutputStream(outputStream);
        stream.writeInt(123);
        stream.writeByte(1);
        stream.writeDouble(3.14);
        stream.close();
        
        FileInputStream inputStream = new FileInputStream("obj.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        System.out.println(objectInputStream.readInt());//读出顺序和吸入顺序需要一致
        System.out.println(objectInputStream.readByte());
        System.out.println(objectInputStream.readDouble());
        
      1. 文件复制的几种方式
        1. 传统IO读写(一个输入流,一个输出流)
        2. NIO中的channel
        3. Apache Commons IO中的FileUtils
        4. Files文件工具类

    8. 网络编程

    1. IntetAddress表示IP地址,有Intet4Address和Intet6Address两个子类

      public static void main(String[] args) throws IOException {
          /**
           * 获取远程的InetAddress 对象
           */
          InetAddress address = InetAddress.getByName("www.baidu.com");
          System.out.println("是否可达: "+address.isReachable(2000));//true
          System.out.println(address.getHostName());//www.baidu.com
          System.out.println(address.getHostAddress());//14.215.177.39
      
          /**
           * 获取本地的netAddress 对象
           */
          InetAddress localHost = InetAddress.getLocalHost();
          System.out.println(localHost.getHostAddress());//10.170.11.18 本地机器的ip地址
          System.out.println(localHost.getHostName());//DESKTOP-GQBJPB6 计算机名称
      }
      
    2. URLDecoder和URLEncoder

      • 当在浏览器输入的网址中含有中文字符时就会使用URL编码,如果能直接看到中文是因为浏览器自动解码了,复制网址重新黏贴就可以看到
      String keyWord="中国";
      String encode = URLEncoder.encode(keyWord, "utf-8");
      System.out.println(encode);//%E4%B8%AD%E5%9B%BD 可以看到是6个字节 因为u8编码每个汉字是3个字节
      String decode = URLDecoder.decode(encode, "utf-8");
      System.out.println(decode);//中国
      
    3. TCP通信

      //服务端代码
      public static void main(String[] args) throws IOException {
              ServerSocket serverSocket = new ServerSocket(9999);
              List<Socket> sockets=new ArrayList<>();
              while (true){
                  Socket accept = serverSocket.accept();
                  sockets.add(accept);//
                  //开启一个单独的线程处理连接上来的客户端
                  //不开启多线程的话 代码会一直在content=bufferedReader.readLine()阻塞 导致不能连接下一个客户端
                  new Thread(()->{
                      try {
                          InputStream inputStream = accept.getInputStream();
                          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                          String content=null;
                          //服务端收发信息都是用到客户端socket的输入输出流
                          while((content=bufferedReader.readLine())!=null){
                              System.out.println("服务端收到: "+content);
                              for (Socket socket : sockets) {
                                  if(socket==accept){
                                      continue;//不发送信息给自己
                                  }
                                  OutputStream outputStream = socket.getOutputStream();
                                  PrintStream printStream = new PrintStream(outputStream);
                                  printStream.println(content);
                                  printStream.close(); //关闭会导致客户端socket
                              }
                          }
      
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }).start();
              }
      
          }
      
      //客户端代码
       public static void main(String[] args) throws IOException {
              Socket socket = new Socket("127.0.0.1", 9999);
              //负责接受消息的线程
              new Thread(()->{
                  try {
                      InputStream inputStream = socket.getInputStream();
                      InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                      BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                      String s=null;
                      while((s=bufferedReader.readLine())!=null){
                          System.out.println(s);
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
      
              }).start();
      
              //主线程负责发送消息
              OutputStream outputStream = socket.getOutputStream();
              //包装成打印流
              PrintStream printStream = new PrintStream(outputStream);
              Scanner scanner = new Scanner(System.in);
              String content=null;
              while((content=scanner.nextLine())!=null){
                  printStream.println(content);
              }
              socket.close();
      
          }
      
    4. 半关闭状态

      • 服务端怎么知道客户端发送数据结束了呢?可以通过直接关闭客户端的socket实现:socket.close(), 但这样会导致一个问题,客户端只是不想发送数据了,关闭socket会导致也不能接受数据
      • shutdownOutput(): 关闭socket的输出流,输入流不受影响
      • shutdownInput(): 关闭socket的输入流,输出流不受影响
      public static void main(String[] args) throws IOException {
          ServerSocket serverSocket = new ServerSocket(9999);
          Socket socket = serverSocket.accept();
          InputStream inputStream = socket.getInputStream();
          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
          String s=null;
          while((s=bufferedReader.readLine())!=null){
              System.out.println(s);
          }
          System.out.println("读取结束...");
          System.in.read();//防止服务器主线程结束
      }
      
      public static void main(String[] args) throws IOException {
          Socket socket = new Socket("127.0.0.1", 9999);
          OutputStream outputStream = socket.getOutputStream();
          PrintStream printStream = new PrintStream(outputStream);
          printStream.println("这是第一条消息");
          printStream.println("这是第二条消息");
          printStream.println("这是最后一条消息");
          System.in.read();//防止客户端主线程结束
      }
      //虽然客户端数据发送完毕了 但是服务器还是一直在等待数据的发送
      //在printStream.println("这是最后一条消息");后加上socket.shutdownOutput();则通知服务器客户度消息已经发送完毕了
      //相当于四次挥手的前两次挥手操作
      
    5. UDP通信

      //服务端
       public static void main(String[] args) throws IOException {
          DatagramSocket socket = new DatagramSocket( 9999,InetAddress.getByName("localhost"));
          DatagramPacket outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName("localhost"), 9998);
          byte[] inBuf = new byte[1024];
          DatagramPacket inPacket = new DatagramPacket(inBuf,inBuf.length);
          while (true){
              socket.receive(inPacket);//接收客户端的消息
              System.out.println("客户端ip: "+inPacket.getAddress());
              System.out.println("客户度端口:"+inPacket.getPort());
              String resp="我是服务端,我收到了消息: "+new String(inBuf,0,inPacket.getLength());
              outPacket.setData(resp.getBytes());//发送消息给客户端
              socket.send(outPacket);
          }
      }
      
      //客户端
      public static void main(String[] args) throws IOException {
              DatagramSocket socket = new DatagramSocket( 9998, InetAddress.getByName("localhost"));
              DatagramPacket outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName("localhost"), 9999);
              byte[] inBuf = new byte[1024];
              DatagramPacket inPacket = new DatagramPacket(inBuf,inBuf.length);
              Scanner scanner = new Scanner(System.in);
              String s=null;
              while((s=scanner.nextLine())!=null){
                  outPacket.setData(s.getBytes());
                  socket.send(outPacket);//发送给服务端
                  socket.receive(inPacket);//接受服务端的消息
                  System.out.println(new String(inBuf,0,inPacket.getLength()));
              }
          }
      //接收包和发送包的端口都是目的端口
      
      • UPD通信时客户端和服务端并没有明显的区分
      • UDP通信步骤:1.创建DatagramSocket 2.创建DatagramPacket(发送包和接收包) 3. send发送 receive接收
      • send receive方法是阻塞的
    6. UPD实现广播通信

      public static void main(String[] args) throws IOException {
          MulticastSocket multicastSocket = new MulticastSocket(9999);
          //广播地址:224.0.0.0~239.255.255.255
          InetAddress address = InetAddress.getByName("230.0.0.1");
          multicastSocket.joinGroup(address);
          multicastSocket.setLoopbackMode(false);
          DatagramPacket outPacket = new DatagramPacket(new byte[0], 0, address, 9999);
          byte[] inBuf = new byte[1024];
          DatagramPacket inPacket = new DatagramPacket(inBuf,inBuf.length);
          new Thread(()->{
              //接收数据
              while (true) {
                  try {
                      multicastSocket.receive(inPacket);
                      System.out.println(new String(inBuf, 0, inPacket.getLength()));
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }).start();
          Scanner scanner = new Scanner(System.in);
          String s=null;
          //发送数据
          while((s=scanner.nextLine())!=null){
              outPacket.setData(s.getBytes());
              multicastSocket.send(outPacket);
          }
      }
      
      • 启动多个客户端,MulticastSocket multicastSocket = new MulticastSocket(9999);, 端口不冲突
    7. 端口问题

      • DatagramSocket ServerSocket Socket MulticastSocket 可以同时共享一个端口
      • UDP通信时客户端服务端端口不能相同
      • TCP通信和UDP通信时可以端口相同

    9. 类加载和反射

    1. java程序运行在JVM进程中,即使这个java程序中有多个线程(一个进程里面可以有多个线程,这个进程就是JVM进程),每运行一次JVM进程,上一次运行时JVM内存中的变量数据都会丢失,可以有多个JVM进程,多个进程之间相互独立,不会共享数据

      Java基础疑难点梳理(泛型到反射9章内容)_第2张图片Java基础疑难点梳理(泛型到反射9章内容)_第3张图片

    2. 类的加载分为三个阶段

      1. 类加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象(类也是一种对象,是Class类实例化得到的对象,通常是说类的实例化得到对象,eg: 名字可以实例化,你的名字叫"李明"(“李明"是对象),我的名字就叫"名字”("名字"是类对象),类加载由类加载器完成
      2. 连接:将类的二进制数据合并到JRE中
        • 验证:class文件格式是否正确
        • 准备:给类的静态变量分配内存并设置初始值
        • 解析:将类的二进制数据中的符号引用替换成直接引用
      3. 初始化:对静态变量初始化赋值(程序员显示设置的值)
        • 如果当前类还没有被加载和连接,先进行加载和连接再初始化
        • 如果当前类的父类还没有被初始化,先初始化父类
        • 类中的初始化语句(静态变量赋值语句 静态代码块)按顺序执行
    3. class文件来源:本地的class文件 jar包中的class文件 从网络加载class文件 把一个Java源文件动态编译并完成加载

    4. 类的初始化时机:

      • 创建类的实例
      • 调用类的静态方法
      • 访问类的静态变量(fianl修饰的在编译期间可以确定下来的常量除外)
        • 可以确定:static fianl String s="hello"
        • 不可以确定:static final String s=System.currentMills()+""
      • 使用反射创建某个类的Class对象
      • 初始化当前类的子类(父类要在子类前初始化)
      • java.exe命令运行某个main方法,main方法所在的类会被初始化
    5. 同一个类只会被JVM加载到内存一次,怎么算同一个类? 由类的全限定类名和类加载器确定,即使全限定类名相同,类加载器不同,也是不同的类

    6. 类加载器分类

      用户自定义加载器
      应用类类加载器
      应用类加载器
      拓展类加载器
      根类加载器

      类加载机制:

      1. 全盘负责:一个类加载器加载某个类时会加载该类所依赖和引用的其他类
      2. 父类委托:先让父类加载,父类无法加载时自己才加载
      3. 缓存机制:加载过的类会被缓存,需要时先从缓存区找,找不到再加载
      public static void main(String[] args) {
              ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();//系统类加载器
              System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
              ClassLoader classLoader = Demo1.class.getClassLoader();//当前类的加载器
              System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
              System.out.println(systemClassLoader==classLoader);//true 当前类的加载器就是系统类加载器
              //获取系统类加载器路径
              //如果配置CLASSPATH环境变量会打印环境变量 否则打印当前类的路径
              URL resource = systemClassLoader.getResource("");
              System.out.println(resource);//file:/D:/IdeaSpace/java_basis/out/production/java_basis/
      
              ClassLoader extendsLoader = systemClassLoader.getParent();
              System.out.println(extendsLoader);//sun.misc.Launcher$ExtClassLoader@74a14482
      
              ClassLoader rootLoader = extendsLoader.getParent();
              System.out.println(rootLoader);//null 根加载器不是java实现的
          }
      
    7. 类加载器加载Class步骤(不包含连接、初始化)

      Class是否已经被加载?
      返回对应的Class对象
      父加载器是否存在?
      父加载器加载Class
      使用根加载器加载
      加载成功?
      加载成功?
      ClassNotFoundException
      使用当前类加载
      从当前类路径中寻找该类
      是否找到?
      从文件中加载Class
    8. 自定义类加载器

      public class MyClassLoader extends ClassLoader{
          //    包路径
          private String Path;
      
          //    构造方法,用于初始化Path属性
          public MyClassLoader(String path) {
              this.Path = path;
          }
      
          //    重写findClass方法,参数name表示要加载类的全类名(包名.类名)
          @Override
          protected Class<?> findClass(String name) throws ClassNotFoundException {
              System.out.println("findclass方法执行");
      
      //        检查该类的class文件是否已被加载,如果已加载则返回class文件(字节码文件)对象,如果没有加载返回null
              Class<?> loadedClass = findLoadedClass(name);
      //        如果已加载直接返回该类的class文件(字节码文件)对象
              if (loadedClass != null){
                  return loadedClass;
              }
      
      //        字节数组,用于存储class文件的字节流
              byte[] bytes = null;
              try {
      //            获取class文件的字节流
                  bytes = getBytes(name);
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
      
              if (bytes != null){
      //        如果字节数组不为空,则将class文件加载到JVM中
                  System.out.println(bytes.length);
      //            将class文件加载到JVM中,返回class文件对象
                  Class<?> aClass = this.defineClass(name, bytes, 0, bytes.length);
                  return aClass;
              }else {
                  throw new ClassNotFoundException();
              }
          }
      
          //    获取class文件的字节流
          private byte[] getBytes(String name) throws Exception{
      //        拼接class文件路径 replace(".",File.separator) 表示将全类名中的"."替换为当前系统的分隔符,File.separator返回当前系统的分隔符
              String FileUrl = Path + name.replace(".", File.separator) + ".class";
              byte[] bytes;
      //        相当于一个缓存区,动态扩容,也就是随着写入字节的增加自动扩容
              ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
              File file = new File(FileUrl);
      //        创建输入流
              InputStream inputStream = new FileInputStream(file);
              int content;
      //        循环将输入流中的所有数据写入到缓存区中
              while ((content = inputStream.read()) != -1){
                  arrayOutputStream.write(content);
                  arrayOutputStream.flush();
              }
              bytes = arrayOutputStream.toByteArray();
              return bytes;
          }
      }
      
      
      public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
          //构造器的参数是类的class文件所在的目录
          MyClassLoader myClassLoader = new MyClassLoader("D:\\IdeaSpace\\java_basis\\out\\production\\java_basis\\");
          Class<?> loadClass = myClassLoader.findClass("test.Hello");
          Class<?> loadClass2 = myClassLoader.findClass("test.Hello");
          System.out.println(loadClass==loadClass2);//true 相同的类加载器某个类只会被加载一次
      
          MyClassLoader myClassLoader2 = new MyClassLoader("D:\\IdeaSpace\\java_basis\\out\\production\\java_basis\\");
          Class<?> loadClass3 = myClassLoader2.findClass("test.Hello");
          System.out.println(loadClass==loadClass3);//false 相同的类 但是是不同的类加载器(创建了两个MyClassLoader对象)
      
      
          Object o = loadClass.newInstance();
          System.out.println(o);//test.Hello@6d6f6e28
          ClassLoader myLoader = loadClass.getClassLoader();
          ClassLoader appLoader = myLoader.getParent();
          ClassLoader extendsLoader = appLoader.getParent();
          ClassLoader rootLoader = extendsLoader.getParent();
          System.out.println(myLoader);//test.MyClassLoader@1540e19d
          System.out.println(appLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
          System.out.println(extendsLoader);//sun.misc.Launcher$ExtClassLoader@7f31245a
          System.out.println(rootLoader);//null
      
      }
      
      
    9. 获得Class对象

      • Class.forName
      • Person.class这种形式
      • p.getClass()
    10. Class对象和普通对象一样,里面有很多方法

      • 可以获得Class对象对应类的构造器
      • 可以获得Class对象对应类的的方法
      • 可以获得Class对象对应类的成员变量
      public static void main(String[] args) {
              Person person = new Person();
              Class<? extends Person> personClass = person.getClass();
              /**
               * 获取Person类中的构造器
               * 输出结果
               * public test.Person()
               * public test.Person(java.lang.String,int)
               */
              Constructor<?>[] constructors = personClass.getConstructors();
              for (Constructor<?> constructor : constructors) {
                  System.out.println(constructor);
              }
      
              /**
               * 获取Person类中的方法
               * 会打印方法签名 不包括父类中的方法
               * static int test.Person.f()
               * void test.Person.info(java.lang.String)
               */
              Method[] methods = personClass.getDeclaredMethods();
              for (Method method : methods) {
                  System.out.println(method);
              }
      
              /**
               * 获取Person类中的属性
               * java.lang.String test.Person.name
               * int test.Person.age
               * static int test.Person.a
               */
              Field[] fields = personClass.getDeclaredFields();
              for (Field field : fields) {
                  System.out.println(field);
              }
          }
      //带Declared的获得的方法或属性和访问权限无关
      //不带Declared只能获得public权限的方法或属性
      
    11. 使用反射创建对象

      Class<Person> personClass = Person.class;
      Person person1 = personClass.newInstance();//调用无参构造器创建对象
      Constructor<Person> constructor1 = personClass.getConstructor();//获取无参构造器
      Person person2 = constructor1.newInstance();
      Constructor<Person> constructor2 = personClass.getConstructor(String.class, int.class);//获取有参构造器
      Person person3 = constructor2.newInstance("tom", 18);
      System.out.println(person1);//Person{name='null', age=0}
      System.out.println(person2);//Person{name='null', age=0}
      System.out.println(person3);//Person{name='tom', age=18}
      
    12. 使用反射调用方法

      Class<Person> personClass = Person.class;
      Person person = new Person();
      Method f1 = personClass.getMethod("f1", int.class);
      Object ret1 = f1.invoke(person, 999);//f1是成员方法 第一个参数需要一个peron对象
      System.out.println(ret1);//返回1
      Method f2 = personClass.getMethod("f2", String.class, int.class);
      Object ret2 = f2.invoke(null, "hello", 123);//f2是一个静态方法 不需要对象 所以第一个参数为null
      System.out.println(ret2);//f2是void类型 返回null
      
      public class Person {
         public int f1(int a){
             System.out.println("a="+a);
             return 1;
         }
         public static void f2(String s,int a){
             System.out.println("s="+s);
             System.out.println("a="+a);
         }
      }
      
    13. 访问成员变量的值

      Class<Person> personClass = Person.class;
      Person person = new Person();
      System.out.println(person.a);//1
      Field a = personClass.getDeclaredField("a");
      a.set(person,111);//通过反射修改a的值 a是成员变量 第一个参数需要传入一个person对象
      System.out.println(person.a);//111
      Field b = personClass.getDeclaredField("b");
      b.setAccessible(true);//b是private 需要先取消权限访问检查
      b.set(person,222);
      
      Field c = personClass.getDeclaredField("c");
      System.out.println(Person.c);//3
      c.set(null,333);//c是静态变量 设置值时第一个参数为null即可
      System.out.println(Person.c);//333
      
      public class Person {
         public int a=1;
         private int b=2;
         static int c=3;
      }
      
    14. 使用反射操作数组

      public static void main(String[] args) throws Exception {
              Object arr = Array.newInstance(int.class, 10);//创建大小为10的int类型数组
              Array.set(arr,1,100);//给数组元素赋值 arr[1]=100
              Array.set(arr,2,200);
              Object ele = Array.get(arr, 1);//获取数组元素的值
              System.out.println(ele);
      
              /**
               * 使用Array获取数组属性
               */
              int[][] nums=new int[3][4];
              getArrayDimensions(nums);
      
          }
      public static void getArrayDimensions(Object arr){
          if(arr.getClass().isArray()){
              System.out.println(Array.getLength(arr));//打印数组维度
              getArrayDimensions(Array.get(arr,0));
          }
      }
      
    15. JDK动态代理步骤

      1. 创建一个接口和实现类(被代理的类)
      2. 创建一个InvocationHandler
      3. 创建代理对象
      4. 代理对象调用方法
      //1. 创建一个接口和实现类(被代理的类)
      interface Dog{
          void info();
          String say(String name,int age);
      }
      class JinMao implements Dog{
      
          @Override
          public void info() {
              System.out.println("我是一只金毛");
          }
      
          @Override
          public String say(String name, int age) {
              return "金毛: name="+name+" age="+age;
          }
      }
      class Keji implements Dog{
      
          @Override
          public void info() {
              System.out.println("我是一只柯基");
          }
      
          @Override
          public String say(String name, int age) {
              return "柯基: name="+name+" age="+age;
          }
      }
      
      //2. 创建一个InvocationHandler
      class MyInvocationHandler implements InvocationHandler{
          Dog dog;//被代理的对象 用接口类型表示 可以接受多个子类对象
      
          public void setDog(Dog dog) {
              this.dog = dog;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println(method.getName()+"方法开始执行...");
      	    System.out.println(proxy.getClass());//class test.$Proxy0
              if(args!=null){
                  System.out.println("参数:"+ Arrays.toString(args));
              }
              Object result = method.invoke(dog, args);//反射调用方法 传入一个对象
              System.out.println(method.getName()+"方法执行结束...");
              return result;
          }
      }
      
      //3. 创建代理对象
      // Dog dog = new JinMao();
      Dog dog=new Keji();
      MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
      myInvocationHandler.setDog(dog);
      //只能强转为接口Dog类型 不能强转为子类类型
      Dog dogProxy = (Dog) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(),myInvocationHandler);
      //4. 代理对象调用方法
      dogProxy.info();
      dogProxy.say("tom",4);
      
    16. 反射与泛型

      Class<Person> personClass = Person.class;
      Field a = personClass.getDeclaredField("a");
      System.out.println(a.getType());//int
      Field intList = personClass.getDeclaredField("intList");
      Field doubleList = personClass.getDeclaredField("doubleList");
      Field map = personClass.getDeclaredField("map");
      System.out.println(intList.getType());//interface java.util.List 
      System.out.println(intList.getGenericType());//java.util.List
      System.out.println(doubleList.getType());//interface java.util.List
      System.out.println(doubleList.getGenericType());//java.util.List
      System.out.println(map.getType());//interface java.util.Map
      System.out.println(map.getGenericType());//java.util.Map
      
      public class Person {
         int a=1;
         List<Integer> intList;
         List<Double> doubleList;
         Map<String ,Integer> map;
      }
      //getType只能获取基本类型的形式 不能获取泛型参数信息 getGenericType可以获取泛型参数信息
      

    你可能感兴趣的:(Java,java,开发语言)