Java 学习大纲
1. public, default, procted, privated 四种修饰符的比较
同一个类中 | 同一个包中 | 不同包的子类 | 不同包的非子类 | |
---|---|---|---|---|
privated | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
- privated:被修饰的变量、方法只能被自身类对象访问,不允许子类和跨包 (package) 访问
- default:只允许在同一个包中进行访问
- protected:被修饰的变量、方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
- public:允许跨包访问
2. 集合类的相关知识
- Java 集合可以分为 Collection 和 Map 两大体系
先上 Collection 继承树结构
图中的虚线箭头表示 implements, 实现箭头表示 extends
-
当集合类中的泛型是自定义类时,一定要重写
equals( )
方法!! -
使用 Set 和 Map 类的时候还必须重写
hashCode()
方法。因为向 Set 或 Map 中添加对象时,首先调用所在类的hashCode()
方法,计算哈希值,若此值对应的位置没有对象,则直接将待添加对象存储到此位置,否则利用equals()
方法进行比较判断是否存在相同对象。- 重写
hashCode()
示例:
public int hashCode() { return Objects.hash(vars); }
- 重写
-
对于 List 和 工具类 Arrays 之间的关系,遇到了一篇 很好的文章,这里附一下链接,以备之后查阅
2.1 Set 相关用法
-
HashSet 底层调用了 HashMap
-
TreeSet 的 自然排序:当向 TreeSet 中添加自定义类对象 o 时,若采用自然排序方式,会依次经历:
compareTo()
hashCode()
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 遍历
- 键遍历:
map.keySet()
- 值遍历:
map.valueSet()
→ \rightarrow → 需要先获得 keySet - 键值对遍历:
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 的子类
注意:
- 静态方法中不能使用类的泛型,因为泛型的实际类型的确定是在实例化之后,即在静态方法加载之后
- 声明为 通配符 的集合类不允许添加元素 ( 除了 null )
4. 注解
-
JDK 提供的三个常用注解:
- @Override:重写父类方法
- @Deprecated:表明该使用情景已过时
- @SuppressWarnings( {“unused”, “…” } ):抑制编译器警告
-
元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解
5. IO 流
5.1 File 相关操作
-
File 对象声明
File file = new File("File_path")
File_path可为绝对路径或相对路径 (为当前工程路径), file 对象可以表示一个文件或一个文件夹
- File 类仅涉及创建、删除以及重命名,不涉及文件内容的改变
5.2 流的分类
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
文本文件可以使用字节流或字符流,但对于非文本文件,只能使用字节流!同时字符流的效率更高
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) // 让当前线程阻塞指定的毫秒数
- Thread 中的常用方法
-
方法二: 实现 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(同步监视器){ // 操作共享数据的代码 }
- 同步监视器:又名锁。任何一个类对象都可以充当锁,但是多个线程必须 共用同一把锁。通常形式为
继承 Thread:synchronized (this.getClass()) { } 实现 Runnable 接口:synchronized (this) { }
- 同步代码块内部相当于单线程
- 同步监视器:又名锁。任何一个类对象都可以充当锁,但是多个线程必须 共用同一把锁。通常形式为
-
方法二:同步方法:将方法添加修饰符 synchronized
- 注意:同步方法仍然涉及到同步监视器!对于非静态方法而言是 this,对于静态方法而言是类本身。因此对于继承 Thread 的类,需要把同步方法声明为
static synchronized
- 注意:同步方法仍然涉及到同步监视器!对于非静态方法而言是 this,对于静态方法而言是类本身。因此对于继承 Thread 的类,需要把同步方法声明为
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
- 常量与常量的拼接结果在常量池
- 只要其中一个是变量,拼接结果就在堆中
- String 类的常用方法可以参考 这篇文章
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> 消费型接口 | T | void | void accept(T t) |
Supplier<T> 供给型接口 | / | T | T get() |
Function<T, R> 函数形接口 | T | R | R apply(T t) |
Predicate<T> 断定型接口 | T | boolean | boolean test(T t) |
9.3 Stream
- Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合 运算和表达的高阶抽象
- 具体用法在这就不细细展开了,可以参考 这篇博客
补充:
-
Stream 的操作不会改变原集合中的元素
-
对于
Stream<T> stream = list.stream()
而言,使用一次后stream
就会关闭,需要重新赋值
9.4 Optional 类
-
为了解决空指针异常
-
使用指南请参考 这篇文章
注意:
Optional.map()
函数的返回值仍为Optional
类型,因此支持链式调用