Bootstrap

Java 学习大纲

Java 学习大纲


1. public, default, procted, privated 四种修饰符的比较

同一个类中同一个包中不同包的子类不同包的非子类
privated
default
protected
public
  • privated:被修饰的变量、方法只能被自身类对象访问,不允许子类和跨包 (package) 访问
  • default:只允许在同一个包中进行访问
  • protected:被修饰的变量、方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
  • public:允许跨包访问

2. 集合类的相关知识

  • Java 集合可以分为 CollectionMap 两大体系

先上 Collection 继承树结构

Collection继承树

图中的虚线箭头表示 implements, 实现箭头表示 extends

  • 当集合类中的泛型是自定义类时,一定要重写 equals( ) 方法!!

  • 使用 SetMap 类的时候还必须重写 hashCode() 方法。因为向 SetMap 中添加对象时,首先调用所在类的 hashCode() 方法,计算哈希值,若此值对应的位置没有对象,则直接将待添加对象存储到此位置,否则利用 equals() 方法进行比较判断是否存在相同对象。

    • 重写 hashCode() 示例:
    public int hashCode() {
            return Objects.hash(vars);
        }
    
  • 对于 List工具类 Arrays 之间的关系,遇到了一篇 很好的文章,这里附一下链接,以备之后查阅

2.1 Set 相关用法

  • HashSet 底层调用了 HashMap

  • TreeSet 的 自然排序:当向 TreeSet 中添加自定义类对象 o 时,若采用自然排序方式,会依次经历:

    1. compareTo()
    2. hashCode()
    3. equals()
    • 因此需要对应类 实现 Comparable 接口,同时重写 int compareTo(Object o); 抽象方法
      @Override
      public int compareTo(Object o){
          /*
          *  进行相关比较
          */
      
          return ret;  // 若return 0,则内部会默认为:当前TreeSet对象中已存在相应实例,即不会添加对象 o
      }
      
  • TreeSet 的 定制排序

Set<T> set = new TreeSet<>(new Comparator<>(){

    @Override
    public int compare(Object o1, Object o2){
        /*
         *  进行相关比较
        */
        
        return ret;
    }
});

2.2 Map 相关用法

Map 继承树结构

  • Map 遍历

    1. 键遍历: map.keySet()
    2. 值遍历:map.valueSet() → \rightarrow 需要先获得 keySet
    3. 键值对遍历:map.entrySet()
  • HashMap 允许使用 null 作为键或值;而 Hashtable 则均不允许,但是其为 线程安全的

2.3 Properties 的使用

Properties p = new Properties();
p.load(new FileInputStream(new File("jdbc.properties")));

String var = p.getProperties("var");

2.4 Collections 相关用法

Collections 是操作集合的工具类,可以操作 Collection 以及 Map

  • 常见 static 方法, 括号内为 参数类型
void reverse(List)
void shuffle(List):List 进行打乱操作
void sort(List, Comparator(可选))
void swap(List, int, int)
void copy(List, List)

T max(Collection)
T min(Collection)

3. 泛型

  • 泛型类的声明:
public class A<T>{
    /*
     *  Declaration of variables and methods
     */
} 
  • 当继承泛型类或泛型接口时,可以指明或不指明泛型的类型,代码模板分别如下
public class B extends A<Integer> { 
   /*
    *  此时B中已经不存在泛型
    */
}

public class B<T> extends A<T>{ /* */ }
  • 当方法的参数类型是其他泛型时,声明模板如下
// 返回变量
public <E> E getEle(E ele) { return ele; }

// 无返回值
public <E> void dealEle(E e) { }
  • 泛型与继承的关系:A 是 B 的子类,但 List<A> 却不是 List<B> 的子接口,但都是 List<?> 的子接口,这里的 ? 是通配符。对于 ? extends E,表示 E 以及 E 的子类

注意

  1. 静态方法中不能使用类的泛型,因为泛型的实际类型的确定是在实例化之后,即在静态方法加载之后
  2. 声明为 通配符 的集合类不允许添加元素 ( 除了 null )

4. 注解

  1. JDK 提供的三个常用注解:

    • @Override:重写父类方法
    • @Deprecated:表明该使用情景已过时
    • @SuppressWarnings( {“unused”, “…” } ):抑制编译器警告
  2. 元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解


5. IO 流

5.1 File 相关操作

  • File 对象声明

    File file = new File("File_path") 
    

File_path可为绝对路径或相对路径 (为当前工程路径), file 对象可以表示一个文件或一个文件夹

  • File 类仅涉及创建、删除以及重命名,不涉及文件内容的改变

5.2 流的分类

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

文本文件可以使用字节流或字符流,但对于非文本文件,只能使用字节流!同时字符流的效率更高

5.2.1. 节点流 (文件流): 直接作用在文件上
  • FileInputStream 使用示例

    File file = new File("...");
    FileInputStream fis = new FileInputStream(file);
    
    byte[] bytes = new byte[1024];
    int len = 0;   
    
    while((len = fis.read(bytes)) != -1){
        String s = new String(bytes, 0, len);   //  此处使用len才能保证合理的读取
        System.out.println(s);
    }
    fis.close();
    
  • FileOutputStream 使用示例

    // 文件不存在会自动创建; 文件存在则会覆盖
    FileOutputStream fos = new FileOutputStream(new File("..."));
    fos.write(new String("...").getBytes());
    fos.close();
    
  • FileReader 使用示例

    FileReader fr = new FileReader("...");
    char[] buff = new char[1024];
    int len = 0;
    
    while((len = fr.read(buff)) != -1){
        System.out.println(new String(buff, 0, len));
    }
    fr.close();
    
  • FileWriter 的使用方法与上述示例类似

5.2.2. 处理流: 包装在已有的节点流之上
  • 缓冲流 (分别与上面四种流相对应): 用于加速节点流的处理速度,提高效率

    • BufferedInputSream

    • BufferedOutputSream

    • BufferedReader

    • BufferedWriter
      使用方法与之前的类似,只是缓冲流的构造函数的参数为节点流

  • 转换流: 实现字符流和字节流的相互转换,从而提高效率 (了解即可)

    • InputStreamReader
    • OutputStreamWriter

    使用模板:

    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("...")));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("...")));
    
5.2.3. 对象流
  • 对象的序列化机制: 把内存中的 Java 对象转化为与平台无关的二进制流,同时也能够通过二进制流来获取原对象

  • 要求: 需要当前类和所有属性的对应类都实现 Serialize 接口


6. 多线程

6.1 多线程的创建方式

  • 方法一: 继承于 Thread 类并重写 run()方法

    注意:要启动多线程,必须调用实例对象的 start( ) 方法,若直接调用 run( ) 方法,相当于在原线程调用了一个普通方法

    • Thread 中的常用方法
      start()                // 启动当前线程并调用 run() 方法
      run()                  // 线程内部执行函数
      currentThred()         // 静态方法,返回当前执行的线程
      getName()              // 获取当前线程的名字
      setName(Stirng name)   // 设置当前线程的名字
      yield()                // 释放当前线程的 CPU 执行权
      A.join()               // 阻塞当前线程,直到 A 线程执行结束
      sleep(long millis)     // 让当前线程阻塞指定的毫秒数
      
  • 方法二: 实现 Runnable 接口

    class MThread implements Runnable{
        @Override
        public void run{
            //...
        }
    }
    
    MThread mthread = new MThread();
    
    // 通过传入同一个实例对象来实现数据共享
    Thread t1 = new Thread(mthread);
    Thread t2 = new Thread(mthread);
    t1.start();  t2.start();
    

开发过程中通常使用方法二

6.2 线程同步

  • 引入关键字 synchronized

  • 方法一:同步代码块

    synchronized(同步监视器){
        // 操作共享数据的代码
    }
    
    • 同步监视器:又名锁。任何一个类对象都可以充当锁,但是多个线程必须 共用同一把锁。通常形式为
      继承 Threadsynchronized (this.getClass()) { }
      实现 Runnable 接口:synchronized (this) { }
      
    • 同步代码块内部相当于单线程
  • 方法二:同步方法:将方法添加修饰符 synchronized

    • 注意:同步方法仍然涉及到同步监视器!对于非静态方法而言是 this,对于静态方法而言是类本身。因此对于继承 Thread 的类,需要把同步方法声明为 static synchronized

6.3 修改后的懒汉式

class Singleton{
    private static Singleton instance = nulll;

    Singleton() { }

    // 双重if校验  
    public Singleton getInstance() {

        // 避免后续到来的线程持续等待
        if(instance == null){  
            synchronized(this.getClass()){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

7. Java的常用类

7.1 字符串相关

7.1.1 String
  • 底层是 final 类型的 char[]: 字符数组中的元素不可修改

  • 通过字面量的方式 (区别于new) 给一个字符串赋值,此时的字符串值应该声明在字符串常量池中,同时字符串常量池中不会存储 相同内容 的字符串

    String s1 = "abc";
    String s2 = "def";
    
    System.out.println(s1 == s2);   // false: 因为比较的是s1和s2的地址值
    
  • 以下二者的区别

String s1 = "abc";
String s2 = new String("abc");

答:第一种方式是从 常量池 中获取对象;而第二种方式的对象在 中创建,但是存储的是 常量池中该字符串的引用。所以 (s1 == s2) == false

  • 解析字符串拼接的结果
String s = "abcd";
String fir = "ab";
String sec = "cd";

String s1 = "ab" + "cd";
String s2 = fir + "cd";
String s3 = "ab" + sec;
String s4 = s2.intern();        // 返回值得到常量池中的 “abcd”

// 结果
s  == s1  
s  != s2
s  != s3
s2 != s3   
s  == s4 
  • 常量与常量的拼接结果在常量池
  • 只要其中一个是变量,拼接结果就在堆中
7.1.2 StringBuffer 和 StringBuilder
  • StringBuffer 是 线程安全 的 (使用了 synchronized 关键字 ),而StringBuilder 是 线程不安全的,但效率高

  • 都继承于 AbstractStringBuilder

  • 具备相同的扩容机制:对于要添加的数据,如果底层数组容量不够,需要进行扩充。默认情况下,扩容为原来的 2 倍 + 2,同时将原有数组中的元素复制到新的数组中

  • 常用方法

StringBuffer append(xxx)
StringBUffer delete(int start, int end)
StringBuffer replace(int start, int end, String str) // 把[start, end) 位置替换为str
StringBuffer insert(int offset, xxx)
StringBuffer reverse()

// 返回值为 StringBuffer 类型表示支持方法链操作

public int indexOf(String str)
public String substring(int start, int end)  // 返回[start, end)的字符串
public int length()
public char charAt(int index)
public void setCharAt(int index, char ch)

7.2 SimpleDateFormat

  • 对 Date 类的格式化和解析

  • 使用模板

// 格式化:Date → String
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormate("yyyy-MM-dd hh:mm:ss");
String formated  = sdf.format(date);

// 解析:String → Date
Date trans = sdf.parse("2021-11-30 10:30:00");

8. 反射

8.1 关于 java.lang.class 类的理解

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

8.2 获取 Class 实例的方式

  • 方法一:调用运行时 类的属性.class

  • 方法二:通过运行时 类的对象,调用 .getClass()

  • 方法三:调用 Class 的静态方法:Class.forName()

第三种方法更常用

  • 对于数组而言,只要数组的元素和维度一样,就是同一个 Class

8.3 相关方法

8.3.1 newInstance()
  • 调用此方法,创建对应的运行时类的对象,内部调用了运行时类的 空参构造器 (同时修饰符为 public)
Class clazz = Class.forName("path");
T t = (T) clazz.newInstance();  // JDK9 后已被注解为 @Deprecated, 更改为 clazz.getDeclaredConstructor().newInstance();
8.3.2 getDeclaredField
{
    Class clazz =Class.forName("...");
    T t = (T) clazz.getDeclaredConstructor().newInstance();

    Field field = clazz.getDeclaredField("...");

    // 对于非 public 的属性,若要修改,需加上
    field.setAccessible(true);

    field.set(t, val);
}

这里的 T 对应具体类型


9. JDK8 及之后的新特性

9.1 Lambda表达式

  • 形式为: ( p a r a m e t e r s ) → s t a t e m e n t s ; (parameters) \rightarrow { statements; } (parameters)statements;

  • Lambda 表达式的本质就是 函数式接口的实例化

  • 仅用于只有 一个 抽象方法的接口,代码形式如下

Runnable r = () -> { System.out.println("Lambda Expr"); }

Comparator<Integer, Integer> com = (o1, o2) -> { Integer.compre(o1, o2); };

Comparator<Integer, Integer> com1 = Integer :: compare; // 与com等价

如果 { } 中的代码只有一行,无论是否有返回值,都可以省略 ({},return,分号), 但是必须一起省略

9.2 Java 内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer<T>
消费型接口
Tvoidvoid accept(T t)
Supplier<T>
供给型接口
/TT get()
Function<T, R>
函数形接口
TRR apply(T t)
Predicate<T>
断定型接口
Tbooleanboolean test(T t)

9.3 Stream

  • Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合 运算和表达的高阶抽象
  • 具体用法在这就不细细展开了,可以参考 这篇博客
补充:
  • Stream 的操作不会改变原集合中的元素

  • 对于 Stream<T> stream = list.stream()而言,使用一次后 stream就会关闭,需要重新赋值

9.4 Optional 类

  • 为了解决空指针异常

  • 使用指南请参考 这篇文章

注意:Optional.map()函数的返回值仍为 Optional类型,因此支持链式调用

;