0%

Java学习笔记

java基础

常用数据类型

  • 基本类型:byte, short, int, long, float, double, boolean, char
  • 引用类型:String, 类

注:

  1. 整数常量默认int型
  2. 对long型常量末尾加lL
  3. 浮点数常量默认double型
  4. 对float型常量末尾加fF
  5. char赋值不能为空(String可以为””):Error: char c = '';
  6. 大转小-位截断

流程控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 条件
if(true){
;
}else if{
;
}else{
;
}
// 循环
for(int i = 0; i < arr.length; i++){
;
}

for(object e: object[]){
;
}

Java面向对象

4种访问权限修饰符

  • private:私有的,只有类的内部可访问
  • 缺省的:与private相比,多了同一个包。
  • protected:保护的,与缺省的相比,多了一个不同包的子类
  • public:公有的,整个工程。

范围:类内部<同一个包<不同包<工程范围(private<缺省<protected<public)。不同包可以创建相同的类不冲突
修饰类的成员:变量、方法
类的修饰:缺省和public

包管理

  • package关键字:申明java文件的包名或路径,”xxx.xxx.xxx”每一个“.”代表分了一层目录。同一个包下,不能有相同的接口和类
  • import关键字:导包必须,显示导入类或接口

import static 导入类或接口中的静态结构:属性或方法

构造器

1
2
3
[public | private | protected] classname (参数列表){
;
}

注:类有默认无参构造器,构造器可以重载,一旦显式提供构造器后,默认无参构造器不再有效

1
2
3
// this 调用构造器
this() // 只能放在首行
// 用于多个构造器的重载
  • 重载:更改同名函数的参数个数和类型实现重载。

继承(Inherit)

  1. 子类拥有父类的非private方法和属性
  2. 子类可以重写父类的方法
  3. 只能单继承
  4. 提高了类间的耦合性
  5. 子类使用super构造器调用父类结构:属性和方法。
  6. 所有类缺省继承lang包下的Object类。
  7. 同名变量的搜寻,就近原则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person{
public String name; // 只有非私有属性,方法被继承
private int age;

public Father(String name, int age){
this.name = name;
this.age = age;
}

public String getName(){
return name;
}
}

class Student{
public String major;

public Student(String name, age, String major){
super(name, age);
this.major = major;
}
}

重写

在子类中对继承的类中的方法进行改造(重写)并覆盖。

  1. 重写的方法修饰符权限 >= 父类中被重写的方法权限
  2. 不能重写private修饰的父类方法
  3. 两者的返回值要么都是void或都相同,要么重写的方法返回值类型是父类中被重写方法的子类,基本类型必须相同。
  4. 子类重写的方法抛出的异常类型不大于父类中被重写的方法抛出的异常类型
  5. static方法不可被重写

同名属性优先使用子类属性,或使用super关键字调用父类属性。子类会默认调用父类的super()空参构造器,一个类的构造器首行只能是super()或this()。

子类对象实例化过程

  1. 子类继承父类后,获取了父类中所有的属性和方法。
  2. 子类创建过程中,直接或间接的通过super关键字调用了父类的构造器。
  3. 只是调用父类的构造器,并没有创建父类的实例

多态(Polymorphic)

  1. 继承
  2. 重写
  3. 父类引用指向子类对象
  4. (向上转型)使用多态方式调用方法时,首先检查父类中是否有该方法,没有则编译错误;如果有,再去调用子类的同名方法
  5. 运行时行为
  6. 虚方法,动态绑定,运行时才确定方法,晚绑定

多态的实现方式:

  • 有继承
  • 有重写
  • 接口
  • 抽象方法和抽象类

向上转型与向下转型(多态性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Father f = new Son();   // 向上转型,**子类结构已经加载到内存**

Son s = (Son)f; // 向下转型
//
Animal a1 = new Bird(); // 向上转型
Animal a2 = new Dog();

System.out.println(a1 instanceof Animal); // true
System.out.println(a1 instanceof Bird); // true

Bird b1 = (Bird) a1; // 向下转型,**内存上是地址的改变** [class@address]
System.out.println(b1 instanceof Animal); // true
System.out.println(b1 instanceof Bird); // true

// 正确
Object ob = new Bird();
Animal a = (Animal)ob;
// **错误**的向下转型
Father f = new Father();
Son s = (Son)f; // error:抛出类型转换错误
  • 向上转型:父类引用指向子类对象(这里的父类可以是接口),不能调用与父类不同的特有的子类方法。
  • 向下转型:使用强制类型转换,可以调用子类的特有方法。
  • a instanceof B: B与B的父类使其等价。

编译看左边,运行看右边。

Object类

  • clone()方法:克隆对象
  • equals()方法:判断对象的值是否相等

== 与 equals

== 运算符:

  1. 可用于基本数据类型和引用数据类型。
  2. 对于基本数据类型的比较,比较的是存储的值。
  3. 对于引用类型的数据,比较的是指向的地址值。

equals方法:

  1. 只适用于引用型数据类型
  2. 用于比较对象间的值是否相同,自定义类需要重写equals方法。(继承自Object,重写前等价于==)

装箱与拆箱

使基本数据类型具有类的特征。主要用于:基本数据类型-包装类-String,三者之间的转换

基本类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

装箱:

1
2
3
4
5
6
7
8
int a = 12;
Integer i = a;

String s = "232";
Integer j = new Integer(s);

Boolean b1 = new Boolean("true"); // true
Boolean b2 = new Boolean("true123"); // false

拆箱:

1
2
Integer i = new Integer(12);
int i = i.intValue(); // xxxValue()方法调用

自动装箱与自动拆箱 (JDK 5.0+)

1
2
3
4
5
6
int a = 12;
method(a); // int->Integer->Object, 自动装箱+向上转型

public void method(Object o){
;
}
  • 自动装箱:Integer a = 12;
  • 自动拆箱:int b = a;
  • 转String转:valueof() 与 parseXXX()
    1. String s = [int|double|float|byte] + "";
    2. String s = String.valueOf([int|double|float|byte]) | String.valueOf(Object)
    3. object o = Object.parseObj(String);

面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 第一题:
Object o1 = true ? new Integer(1) : new Double(1.2);
sysout(o1) // 1.0 自动类型提升

// 第二题:
Integer aa = new Integer(1);
Integer bb = new Integer(1);
System.out.println(aa == bb); // false:两个对象的地址不同

Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true: Integer的内部类IntegerCache中有个int型的cache数组,里面存了-128~127这些数,该范围的数事先加载好了,每个数都有一个地址值。故在该范围的数,地址都相同

Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false:超过了-127-128这个范围,故都是要new一个Integer对象,故地址不同


// Integer的私有静态内部类(私有的构造器),且有一个静态块
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

static修饰符

static:静态

  1. 静态:不随外部的变化而变化,被所有对象共有,属于类本身
  2. 可以修饰:属性、方法、代码块、内部类
  3. 静态成员随着类的加载而加载
  4. 静态变量不仅可以通过类调用
  5. 由于类只加载一次,故静态变量在内存中只有一份,存在方法区的静态域中
  6. 静态方法中不能使用this、super、非静态变量。(生存周期的角度去理解)

单例模式

私有化构造器,使用类变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 饿汉式: 提前新建好了,对象加载时间过长,但是线程安全
class Singleton{
private static Singleton s = new Singleton(); // 私有类变量

private Singleton(){ // 私有构造器
;
}

public static getInstance(){ // 静态方法
return s;
}
}

// 懒汉式: 需要时才新建对象,延迟对象的创建,线程不安全
class Singleton{
private static Singleton s = null;

private Singleton(){

}

public static getInstance(){
return s;
}
}

代码块

  1. 存在于类内部,属于类成员。用来初始化类、对象
  2. 只能用static修饰,或者不修饰
  3. 静态代码块属于类,非静态代码块属于对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
main(){
String s = Block.s; // 输出 static code block
Block b1 = new Block(); // 输出 code block
Block b2 = new Block(); // 输出 code block
Block b3 = new Block(); // 输出 code block
}

class Block{
static String s = "Block";

public Block(){}

/*
静态代码块:
1. 随着类的加载执行一次,后不再执行(按照声明的先后顺序执行多个)
2. 只能调用静态的成员。
3. 不能调用非静态结构
*/
static{
sysout("static code block");
}

//
/*
非静态代码块:
1. 随着对象的创建执行,每次创建一次则执行一次(先执行代码块,再执行构造器,可以对对象进行初始化)
2. 静态、非静态都可以调用。
*/
{
sysout("code block");
}
}

赋值顺序:默认赋值->(显示赋值->代码块赋值)->构造器

final修饰符

  • final修饰类: 不能被继承,不能被拓展,最终形态。
  • final修饰方法:不能被重写,最终版本。
  • final修饰变量:一旦被赋值,则不可修改。常量。

类中的final变量初始化:显式、构造器、代码块。即在对象出生前,要被初始化

抽象(Abstract)

如果一个类中没有包含足够的信息来描绘一个具体的对象,那这个类就是抽象的。

抽象方法

  • 只属于抽象类:如果一个类包含抽象方法,那么该类必须是抽象类
  • 非抽象类(具体类)必须重写:任何子类必须重写父类的所有抽象方法,或者声明自身为抽象类
  • 没有方法体:声明为抽象方法时,不含方法体

抽象类

  • 抽象类不能被实例化。只有抽象类的非抽象子类可以创建对象
  • 抽象类不一定包含抽象方法,包含抽象方法的类一定是抽象类
  • 可以是“模板类”,可以调用抽象方法。

匿名子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
main(){
Male m = new Male();
func(m);

func(new Male()); // 匿名对象

// 创建了匿名类的对象p:向上转型
Person p = new [匿名 extends]Person(){ // 抽象类的匿名子类
@override
public void eatFood(){
;
}
};

}

static void func(){
;
}

// 抽象类
abstract class Person{
public abstract void eatFood();
}
// 子类
class Male{
@override
public void eatFood(){
;
}
}

封装(Encapsulation)

  1. 良好的封装可以减少耦合
  2. 类内部的结构可以自由修改
  3. 对成员变量精确控制
  4. 隐藏信息,实现细节

接口(Interface)

接口与类是并列的结构。

接口与类的区别

  • 接口不是类,没有构造器
  • 接口中的所有方法必须是抽象方法
  • JDK7.0-:只能有全局常量(public static final, 可省略)和抽象方法,JDK8.0+:增加了静态方法和默认方法

接口特性

  • 接口中每一个方法被隐式指定且只能是public abstract
  • 接口中的变量会被隐式指定且只能是public static final
  • 接口中的方法不包含方法体,且只有两种结果:
    1. 被抽象类实现,可以不含方法体
    2. 被具体类实现,必须重写方法体
  • 接口可以extends多继承多个接口

接口的多态性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
main(){
USB usb = new Printer(); // 接口实现类的向上转型
transferData(usb);
transferData(new Disk());
}

static transferData(USB u){ // 多态
u.do()
}

interface USB{
void do();
}

class Printer implements USB{
@Override
void do(){

}
}

class Disk implements USB{
@Override
void do(){

}
}

总结:接口是方法的规范,面向接口编程。

与抽象类的区别

  • 抽象类中的方法可以有方法体,但是接口中所有的方法不能有方法体
  • 一个类只能继承一个抽象类,而一个类可以实现多个接口
  • 接口没有构造器

匿名实现类

1
2
// 向上转型,多态
Interface interface = new [匿名] Interface{...}; // 匿名内部类,其实现了接口。省略号中包含方法体

总结:接口就是抽象方法的集合

代理模式(Proxy)

两个类实现同一个接口,代理类中有被代理类的对象,代理类代表被代理类的功能,屏蔽真实的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ProxyPattern {
public static void main(String[] args) {
RealImage realImage = new RealImage();
ProxyImage proxyImage = new ProxyImage(realImage);
proxyImage.show();
}
}
// 接口
interface Image{
void show();
}
// 被代理类
class RealImage implements Image{
@Override
public void show() {
System.out.println("show 真实的图片");
}
}
// 代理类
class ProxyImage implements Image{
private Image image;

public ProxyImage(Image i){ // 多态
this.image = i;
}

@Override
public void show() {
System.out.println("检查图片");
this.image.show();
}
}

工厂模式

实现了创建者与调用者的分离(开闭原则:对拓展开放,对修改封闭)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 工厂方法模式
public class FactoryPattern {
public static void main(String[] args) {
AudiFactory audiFactory = new AudiFactory();
BMWFactory bmwFactory = new BMWFactory();
Car a = audiFactory.getCar();
Car b = bmwFactory.getCar();
a.run();
b.run();
}
}

interface Car{
void run();
}

class Audi implements Car{
@Override
public void run() {
System.out.println("奥迪在跑");
}
}

class BMW implements Car{
@Override
public void run() {
System.out.println("宝马在跑");
}
}

interface Factory{
Car getCar();
}

class AudiFactory implements Factory{
@Override
public Car getCar() {
return new Audi();
}
}

class BMWFactory implements Factory{
@Override
public Car getCar() {
return new BMW();
}
}

JDK8.0+的接口新特性

接口中的成员:

  1. 全局常量
  2. 抽象方法
  3. 静态方法(默认public,8.0+)
  4. 默认方法(默认public,8.0+)
1
2
3
4
5
6
7
8
9
interface Test{
public static void print(){
sys("s")
}

public default void dprint(){
sys("f")
}
}
  • 静态方法:只能使用接口调用
  • 默认方法:可以重写方法体,也可以不重写使用默认的方法体。
    注:父类与接口中的方法同名同参数,那么子类在没有重写的情况下默认调用父类方法。(类优先,若是同名属性则不被允许,多个接口中的同名默认方法冲突否则必须重写。若要调接口中的方法—InterfaceA.super.method

内部类(不常见)

  • 局部内部类:方法体内、代码块内。
  • 成员内部类:类的下一层,与成员变量平级

成员内部类

  • 可以调用外部类的结构
  • 可以被static修饰
  • 可以有4种修饰方式

局部内部类

  • 接口实现类
  • 匿名实现类。
  • 局部内部类调用外部的局部变量,要求其是final的。

面向对象总结

  1. abstract:可以修饰类和方法,类是抽象的不能被实例化,方法是抽象的没有方法体需要具体类实现
  2. 接口相互之间可以继承,抽象类可以实现接口,抽象类可以继承非抽象类
    3.抽象与接口体现了多态

异常

  • 编译时异常
  • 运行时异常

异常体系结构

1
2
3
4
5
6
7
8
9
10
11
java.lang.Throwable
|----java.lang.Error
|----java.lang.Exception
|----编译时异常:
- IOException
- ClassNotFountException
|----运行时异常
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException

异常处理方式

  1. try-catch-catch-...-finally:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try{
    // 可能会出现异常的代码
    }catch(异常类型1 变量1){
    // 发生异常1时,处理异常的代码
    }catch(异常类型2 变量2){
    // 发生异常2时,处理异常的代码
    }finally{
    // 无论对错,始终一定都会执行的代码,即使catch里有return语句,通常用于关闭资源
    }

    异常子类的catch必须在异常父类的上面,否则报错。

  2. 异常类型方法:
    • getMessage(): 返回一个String
    • printStackTrace(): void方法,输出栈追踪
  3. method() throws 异常类型: 不处理异常,将异常抛给调用者,表示该方法可能抛出一个异常
  4. throw new [异常类型1,2...]:抛出异常
  5. 子类重写父类方法抛出的异常只能是父类异常的子异常。

自定义异常

  1. 继承已有的异常类
  2. 提供全局常量标识类
  3. 提供多个构造器

多线程

  • 程序:实体
  • 进程:运行中的程序,是资源分配的基本单位
  • 线程:进程的细分,可理解为执行路径,是调度的基本单位
  • 并行:多个CPU同时执行多个任务,CPU的并行
  • 并发:一个CPU同时执行多个任务,任务的并发
  • 一个Java程序至少有3个线程:main,垃圾收集,异常处理

通过Thread类创建

  1. 继承java.lang.Thread类
  2. 重写run方法,是线程执行的代码
  3. 创建Thread类的子类对象
  4. 通过对象调用start方法
  5. start方法只能被同一个线程调用一次(IllegalThreadStateException)
  6. 使用匿名子类使用不同的run方法

Thread常用方法

  1. start方法:启动线程
  2. run方法:线程的执行内容
  3. currentThread方法:获取执行当前代码的线程
  4. setName、getName方法:命名的修改与获取
  5. yield方法:释放对CPU的占用,回到就绪状态
  6. join方法:阻塞执行当前代码的线程,等待调用方法的线程执行完毕后,结束阻塞状态
  7. stop方法:以及弃用,结束线程的生命周期
  8. sleep方法(静态):当前线程“睡眠”毫秒数(1秒=1000毫秒),使线程阻塞
  9. isAlive方法:线程是否存活

线程的调度

  • 时间片
  • 优先级

优先级:从概率上来讲优先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;

通过Runnable接口创建

  1. 创建实现Runnable接口的类
  2. 实现抽象方法:run
  3. 创建该类的对象
  4. 将此对象传入Thread的构造器中,创建Thread的对象
  5. 通过Thread的对象调用start方法

继承Thread类影响了拓展性,而实现Runnable接口则不会影响拓展性

两者对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// WindowsTest继承了Thread类,创建3个对象来建立3个线程
WindowsTest w1 = new WindowsTest();
WindowsTest w2 = new WindowsTest();
WindowsTest w3 = new WindowsTest();
w1.start();
w2.start();
w3.start();

// WindowsRunnable实现了Runnable接口,创建1个对象来建立3个线程
WindowsRunnable wr = new WindowsRunnable();
Thread t1 = new Thread(wr);
Thread t2 = new Thread(wr);
Thread t3 = new Thread(wr);
t1.start();
t2.start();
t3.start();
  • Thread也重写了Runnable接口
  • 第二种方式相当于覆盖了Thread的run方法

线程的状态

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

线程状态转换图:
线程的生命周期

线程的同步

问题:

  1. 当线程之间存在共享数据时,就会存在安全问题
  2. 共享数据成为临界资源,访问临界资源的代码称为临界区

如何解决:

  1. 对线程的访问的资源进行加锁,加锁后其他进程不能访问

java的解决方式:

  • synchronized关键字:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 同步代码块
    synchronized(同步监视器){
    // 临界区
    }

    // 同步方法:Runnable实现类-this为同步监视器
    权限修饰符 synchronized void method(){
    // 临界区
    }
    // 同步方法:Thread子类-ClassName.class为调用者,且只有一个
    权限修饰符 static synchronized void method(){
    // 临界区
    }
  • 同步监视器:锁-任何一个对象都可以作为锁;要求-多个线程必须共用一把锁

  • this作为锁:适用于实现Runnable接口的类的对象,因为只创建了一个对象
  • ClassName.class作为锁:该对象只会加载一次
  • 在Runnable实现类中考虑使用this为锁,在Thread的子类中考虑使用ClassName.class作为锁

同步的缺点:

  1. 使用了线程同步相当于,线程的串行执行,速度慢

线程安全的单例模式

  • 懒汉式:线程安全

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class LazySingleton{
    private static LazySingleton s = null;

    private LazySingleton() {}

    public static synchronized LazySingleton(){
    if(s == null){
    s = new LazySingleton();
    }
    return s;
    }
    }
  • 懒汉式:双重校验锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class LazySingleton{
    private static LazySingleton s = null;

    private LazySingle() {}

    public static LazySingleton getInstance(){
    if(s == null){
    synchronized(LazySingleton.class){
    if(s == null){
    s = new LazySingleton();
    }
    }
    }
    return s;
    }
    }

死锁

不同的线程分别占用对方的资源不放弃,都在等待对方去释放自己需要的同步资源,就形成了死锁。可见申请资源的方向称为一个环状结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 线程A
new Thread({
LockA{
// 抢占A锁,等待B锁
LockB{

}
}
}).start()
// 线程B
new Thread({
LockB{
// 抢占B锁,等待A锁
LockA{

}
}
})

Lock锁

  • Lock对象:JDK5.0+,作为同步锁
  • java.util.concurrent.locks.Lock接口
  • ReentrantLock类实现了Lock接口

synchronized与lock的不同:

  1. synchronized相当于自动锁,自动加锁,自动释放锁;lock手动启动锁,手动释放锁
  2. 优先使用顺序:lock锁->同步代码块->同步方法

线程通信

  • wait方法:阻塞当前线程,并释放锁
  • notify方法:唤醒用wait阻塞的线程
  • notifyAll方法:唤醒所有wait阻塞的线程
  • 方法的调用者必须是同步代码块或同步方法的同步监视器,否则出现异常IllegalMonitorStateException
  • 3个方法都来自Object,因为同步监视器可以是任何对象,他们的共同父类是Object

生产者消费者问题:使用wait与notify让生产者与消费者之间产生交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package ind.MultiProcess;

/**
* 生产者与消费者的问题
*/
public class ConsumerAndProducerProblem {
public static void main(String[] args) {
StoreManagement store = new StoreManagement();
store.openStore();
}
}

class Store {
final int stocks_num = 20;
int stocks_now = 0;

}

class Producer implements Runnable {
private final Store store;

public Producer(Store store) {
this.store = store;
}

@Override
public void run() {
while (true) {
produceItem(Thread.currentThread().getName());
}
}

private void produceItem(String name) {
try {
synchronized (store) {
if (store.stocks_now < store.stocks_num) {
Thread.sleep(1000);
store.stocks_now++;
store.notify(); // 唤醒消费者
System.out.println(name + ": 当前货量-" + store.stocks_now);
} else {
store.wait(); // 货满,生产者等待
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class Consumer implements Runnable {
private final Store store;

public Consumer(Store store) {
this.store = store;
}

@Override
public void run() {
while (true) {
consumeItem(Thread.currentThread().getName());
}
}

private void consumeItem(String name) {
try {
synchronized (store) {
if (store.stocks_now > 0) {
Thread.sleep(500);
store.stocks_now--;
store.notify(); // 唤醒生产者
System.out.println(name + " 消费后剩余" + store.stocks_now);
} else {
store.wait(); // 货架为空,消费者等待
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class StoreManagement {
private final Store store = new Store();
private final Producer producer = new Producer(store);
private final Consumer consumer = new Consumer(store);

public void openStore() {
initProducers(); // 开启生产者
initConsumers(); // 开启消费者
}

private void initProducers() {
final Thread[] producers = new Thread[5]; // 5个生产者
for (int i = 0; i < producers.length; i++) {
producers[i] = new Thread(producer);
producers[i].setName("生产者" + i);
}
for (Thread thread : producers) {
thread.start();
}
}

private void initConsumers() {
int n = (int) (3 + Math.random() * 5);
System.out.println(n + "个消费者");
System.out.println("5个生产者");
final Thread[] consumers = new Thread[n]; // 3-8个消费者
for (int i = 0; i < consumers.length; i++) {
consumers[i] = new Thread(consumer);
consumers[i].setName("消费者" + i);
}
for (Thread thread : consumers) {
thread.start();
}
}
}

JDK5.0+新增方式

  • 实现Callable接口
  • 使用线程池

Callable接口:

  • 创建Callable接口的实现类
  • 借助FutureTask类创建对象,FutureTask类实现了Runnable接口
  • 用FutureTask类的get方法获取线程执行call的返回值
  • 同样将FutureTask类的对象来构造Thread,来启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CallableTest {
public static void main(String[] args) {
CallThread t = new CallThread(); // Callable接口的实现类
FutureTask ft = new FutureTask(t); // 继承过Runnable接口
new Thread(ft).start();
try {
Object o = ft.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

class CallThread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
sum += 2;
System.out.println(i);
}
}
return sum;
}
}

优点:

  • 有返回值
  • 可以抛出异常
  • 支持泛型

使用线程池:

  • 提高响应速度
  • 降低资源消耗
  • 便于线程管理
  1. ExecutorService:线程池接口

    1
    2
    3
    4
    5
    6
    7
    ExecutorService service = new ExecutorService(10);  // 创建10个线程的线程池
    void execute(Runnable command): 执行任务,Runnable接口的实现类
    void shutdown(): 关闭连接池
    =========================
    corePoolSize: 核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时,最多保持多少时间后会终止
  2. Executors:线程池的工厂类、工具类。静态方法

Java常用类

String类

  • final类
  • 实现了java.io.Serializable, Comparable<String>, CharSequence三个接口
    1. Serializable接口:表示支持序列化
    2. Compareable<String>接口:比较大小
  • final char[]:用于存储字符串数据
  • 不可变的字符序列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    String s1 = "123";  // 字面量,字符串值存储在字符串常量池中,其中不会存储相同的常量字符串
    String s2 = "123";
    String s3 = new Stirng("123") // 存储在堆中

    sysout(s1 == s2) // true
    sysout(s1 == s3) // false

    Person p1 = new Person("Jack", 12)
    Person p2 = new Person("Jack", 113)
    sysout(p1.name == p2.name) // true 因为"Jack"为字面量,存储在常量池中

    String s1 = "javaEE";
    String s2 = "hadoop";

    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    System.out.println(s3 == s4); // true,常量与常量的拼接都在常量池中
    System.out.println(s3 == s5); // false
    System.out.println(s3 == s6); // false
    System.out.println(s5 == s6); // false
    // 只要其中一个是变量,结果就在堆当中

    String s7 = s5.intern() // 返回在常量池中的字符串
    sysout(s3 == s7) // true

    注意事项:

    1. 拼接操作会产生新的字符串常量
    2. replace操作也会产生新的字符串常量

JVM内存结构:
JVM内存结构

String常用方法

  • int length():返回字符串长度
  • char charAt(int index):返回某索引处的字符
  • boolean isEmpty():判断字符串是否为空
  • String toLowerCase()
  • String toUpperCase()
  • String trim():返回字符串的副本,忽略前后空格
  • boolean equals(Object obj):比较字符串的内容是否相同
  • String concat(String str):等价于“+”
  • int CompareTo(String anotherString):比较字符串中每个字符的Ascii大小

  • String substring(int beginIndex)
  • boolean startWith(String suffix)
  • boolean endWith(String prifix)
  • boolean contains(CharSequence s)
  • int indexOf(String str)
  • int lastIndexOf(String str)

  • String replace(String oldStr, String newStr)
  • String replaceAll(String regex, String replacement)
  • String replaceFirst(String regex, String replacement)
  • boolean matches(String regex)
  • String split(String regex)
  • String转其他类型:parseXxx(String str)
  • 其他类型转String:String.valueOf(Xxx)
  • String转为char数组:str.toCharArray()
  • char数组转为String:调用String的构造器
  • String转为byte数组:str.getBytes(String charset),使用默认字符集
  • byte数组转为String:调用String的构造器,使用编辑器默认字符集,或者利用参数指定字符集

StringBuffer(效率低)与StringBuilder(效率最高)

  • String:不可变的字符序列;使用char数组存储数据
  • StringBuffer:可变的字符序列线程安全的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    StringBuffer sb = new StringBuffer();  // 底层创建一个长度为16的char数组
    sb.append("hello") // 数组的扩容
    /*
    底层是将 原字符串的长度*2+2 作为新字符串的长度,同时赋给新的数组copyof
    */
    private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
    value = Arrays.copyOf(value,
    newCapacity(minimumCapacity));
    }
    }

    private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
    newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
    ? hugeCapacity(minCapacity)
    : newCapacity;
    }
  • StringBuilder:可变的字符序列,线程不安全的。jdk5.0+;底层原理同StringBuffer,两者继承自同一父类。

常用方法:

  1. append
  2. replace
  3. insert
  4. reverse
  5. setCharAt
  6. delete

日期和时间

时间

  • System类中的currentTimeMillis():返回当前的时间戳毫秒数,返回long型

日期

  • java.util.Date
  • java.sql.Date:继承自前者

转换:利用时间戳来相互转换

SimpleDateFormat格式化日期

1
2
3
4
5
6
7
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat();
String s = sdf.format(date) // 日期---->字符串 format

SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String s1 = "2000-12-1 23:22";
Date date1 = sdf1.parse(s1); // 字符串----->日期 parse

Calender抽象类的使用 JDK8.0-

1
2
3
4
5
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass()); // class java.util.GregorianCalendar
System.out.println(calendar.get(Calendar.HOUR)); // get
calendar.set(Calendar.MONTH, 9); // set
calendar.add(Calendar.MONTH, -2); // add

JDK8.0+ 时间API

常用类(没有偏移量了;不可变性):

  • LocalDate
  • LocalTime
  • LocalDateTime
  • Instant
  • DateTimeFomatter

方法:

  • withxxx():返回带有指定值的新的日期对象
  • plusxxx()
  • minusxxx()

比较器

两个接口

  • Compareable(内比较器):实现类需重写compareTo方法
  • Comparator(外比较器):定制排序,重写compare方法

System、Math、BigInteger、BigDecimal

  • System:有关系统的
  • Math:有关数学运算的
  • BigInteger:表示不可变的任意精度的整数
  • BigDecimal:表示不可变的、任意精度的有符号十进制定点数。精度较高。

枚举类

有限个、确定的值的类。(JDK5-自定义,JDK5+引入enum关键字)

自定义枚举类

  1. 私有化每个枚举值
  2. 私有化构造器
  3. 创建全局枚举对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Season{

public final String season;

private Season(String name){
this.season = name;
};

public static final Season SPRING = new Season("春天");
public static final Season SUMMER = new Season("夏天");
public static final Season AUTUMN = new Season("秋天");
public static final Season WINTER = new Season("冬天");

public String getSeason() { // 获取枚举变量的值
return season;
}

@Override
public String toString() {
return "Season{" +
"season='" + season + '\'' +
'}';
}
}

使用enum关键字

  • 继承自java.lang.Enum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum SeasonStatus{
// objName
SPRING("春天"), // 逗号隔开
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天"); // 分号结束

private final String name;
SeasonStatus(String name){
this.name = name;
};

@Override
public String toString() {
return super.toString();
}

public String getName() {
return name;
}
}

枚举类的方法:

  • values()
  • valueOf(String objName)

枚举类实现接口:

  • 同类一样实现接口方法
  • 每个对象可以不同地实现接口方法

注解(Annotation)JDK5.0+

  • @override:限定重写父类的方法
  • @Deprecated:过时的
  • @SuppressWarnings:抑制编译器警告

自定义注解:参照@SuppressWarnings;有成员value,或无成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}

元注解

现有的注解进行解释说明的注解:

  • Retention(保留):用于指定注解的生命周期-SOURCE\CLASS(default)\RUNTIME
  • Target:指定注解能用于修饰那些元素-TYPE/FIELD/METHOD/CONSTRUCTOR… …
  • Documented:令注解在被javadoc解析后被保留下来
  • Inherited:令注解具有继承性

JDK8.0+新特性

  • 可重复注解
  • 类型注解:TYPE_USER/TYPE_PARAMETER

集合

  • Collection接口:单列数据,单一对象
    1. List:有序、可重复
    2. Set:无序、不可重复
  • Map接口:双列数据,存储key-value
    1. HashMap
    2. LinkedHashMap
    3. TreeMap
    4. HashTable
    5. Properties
  • Collections工具类

Collection接口常用方法:

  • add(Object e):添加一个元素
  • addAll(Collection coll):添加一个集合中的所有元素
  • size():集合的大小
  • isEmpty():集合是否为空
  • clear():清空集合
  • contains(Object e):是否存在元素,使用equals来比较元素
  • containsAll(Collection coll):判断集合的元素是否都存在
  • remove(Object e)
  • removeAll(Collection coll)
  • retainAll(Collection coll1):求两个集合的交集,并替换this集合
  • Object.equals()
  • Object.hashCode()
  • toArray():集合—>Object数组
  • Arrays.asList():数组—>集合
  • iterator():放回Iterator接口的实例

注:添加的obj要重写equals方法

Iterator接口

迭代器,用于遍历集合中的元素,只能用一次。不可逆。

  • E next():指针下移
  • boolean hasNext():指针不变
  • void remove():删除集合中的元素()
1
2
3
4
// 常用遍历
while (iterator.hasNext()){
System.out.println(iterator.next());
}

List接口(Collection子接口)

元素有序、可重复、动态变化大小。List实现类:

  • ArrayList:线程不安全,效率高;底层用Object[]数组
  • LinkedList:插入、删除效率高;底层使用双向链表实现
  • Vector:线程安全,效率低;底层用Object[]数组

ArrayList

JDK7.0:

1
2
3
ArrayList arr = new ArrayList();  // 容量默认为10的Object数组
// add操作会让判断数组大小,扩容机制:变为原来的1.5倍,并赋值给新的数组
// 建议使用带参数的构造器,就避免去频繁的动态扩容

JDK8.0+:

1
2
3
4
5
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // {}空的
}
// 调用add操作后才开始创建数组,其它与JDK7无异
// 延迟了数组的创建时间

LinkedList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
transient Node<E> first;
transient Node<E> last;

// 静态内部类,前后指针
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

// add操作
void linkLast(E e) {
final Node<E> l = last; // 最后一个节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode; // 新节点作为尾结点
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

Vector

基本不用了

List接口常用方法

  • void add(int index, Object ele)
  • boolean addAll(int index, Collection eles)
  • Object get(int index)
  • int indexOf(Object obj):如果不存在返回-1
  • int lastIndexOf(Object obj)
  • Object remove(int index) / boolean remove(Object o):按照索引删除,并返回此元素
  • Object set(int index, Object ele),按照索引修改,并返回此元素
  • List subList(int fromIndex, int toList)

Set接口(Collection子接口)

  • 存储无序的:不等于随机性,体现在哈希值上,根据哈希值存储。
  • 不可重复的:利用hashCode方法与equals方法判断是否重复。底层:数组+链表,哈希值与equals方法,在数组中的位置与哈希值有关(JDK7与8不一样,对链表的操作分为头插法8+与尾插法7-)

实现类:

  • HashSet:主要实现类,线程不安全,可以存储null值,底层是一个数组,长度为16
    • LinkedHashSet:HashSet的子类,可以按照顺序变量。原理依然是数组+链表,不过链表的节点的双向量表,记录的是添加的顺序。遍历的效率高
  • TreeSet:空参构造底层是一个TreeMap,可以对属性排序。只能添加同类型的数据,并自然排序(实现Comparable接口的类)。根据compareTo来判断对象是否相同(返回0)。
    • 定制排序:TreeSet treeset = new TreeSet(Comparator com),使用比较器

所以一般需要重写hashCode方法与equals方法:相等的对象要有相等的哈希值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 面试题:理解hashSet利用哈希值存储、不可重复性的特点
Person p1 = new Person("Jack", 12);
Person p2 = new Person("Tom", 22);

hashSet.add(p1);
hashSet.add(p2);

System.out.println(hashSet); // [Person{name='Tom', age=22}, Person{name='Jack', age=12}]

p1.age = 11;
System.out.println(hashSet.remove(p1)); // false
System.out.println(hashSet); // [Person{name='Tom', age=22}, Person{name='Jack', age=11}]
hashSet.add(new Person("Jack", 11));
System.out.println(hashSet); // [Person{name='Tom', age=22}, Person{name='Jack', age=11}, Person{name='Jack', age=11}]
hashSet.add(new Person("Jack", 12));
System.out.println(hashSet); // [Person{name='Tom', age=22}, Person{name='Jack', age=11}, Person{name='Jack', age=11}, Person{name='Jack', age=12}]

Map接口(Collection子接口)

双列数据,key-value型数据。实现类

  • HashMap:常用实现类。线程不安全的。有null的key与value
    • LinkedHashMap:继承自HashMap,可以按照添加的顺序遍历
  • TreeMap:可以按照添加的顺序排序(按照key排序,要求是同一个类)遍历,底层使用红黑树
  • Hashtable:古老类。线程安全。不能有null的key与value
    • Properties:用来处理配置文件,key与value都是String型的

HashMap

底层概述:

  • JDK7-:数组+链表
  • JDK8+:数组+链表+红黑树
  • Key:无序,不重复。使用set存储Key,需要重写equals方法与hashCode方法
  • Value:无序,可重复。
  • Key-Value:一个Entry

JDK7:Entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
HashMap hashMap = new HashMap();  // 默认16长度的Entry数组,数组+链表
// 添加
hashMap.put(key, value) // 使用key的hashCode计算哈希值,按此哈希值计算存储位置。与Set类似,key的去重过程为先比较哈希值,再使用equals方法比较。
// 如果存在相同的key,则用新的value替换。从始至终都是比较的key

// 扩容:当超出临界值,且存放位置不为空
// 扩容为原来的2倍,并将原来的数据复制过来

//-------------------源码-------------------------------
public HashMap(){ // 16, 0.75
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); // 默认初始化容量16,默认加载因子0.75
}

this(int initialCapacity, int loadFactor){
...
int capcity = 1
while (capacity < initialCapacity){
capacity <<= 1 // 2倍增加
}
...
// 临界值,影响扩容时的条件 16*0.75=12
threshold = (int)Math.min(capacity*loadFactor, MAXIMUM_CAPACITY + 1)
}
// 扩容操作
// 头插法, 新节点next指向当前链表的头部

Entry(int h, K k, V v, Entry<K, V> n){
value = v;
next = n;
key = k;
hash = h;
}

JDK8:Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
HashMap hashMap = new HashMap();  // 底层没有创建数组,Node,数组+链表+红黑树
// 添加,首次调用时,再创建
hashMap.put(key, value) // 使用key的hashCode计算哈希值,按此哈希值计算存储位置。与Set类似,key的去重过程为先比较哈希值,再使用equals方法比较。
// 如果存在相同的key,则用新的value替换。

// 扩容:容量*2,且加载因子*2
// 当某个索引位置处链表长度 > 8 且当前数组长度 > 64时,将此索引位置上的结构转变为红黑树。

//-------------------源码-------------------------------
public HashMap() { // 加载因子0.75,用于扩容临界值
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

// put操作
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) // 第一个元素
n = (tab = resize()).length; // 初始化16
if ((p = tab[i = (n - 1) & hash]) == null) // 找到需要插入的位置上是否为空
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k; // 创建一个临时变量
if (p.hash == hash && // 比较两者的哈希值是否相同
// 再比较key的地址是否相同,再equals比较内容是否相同
((k = p.key) == key || (key != null && key.equals(k))))
e = p; // 哈希值相同且key相同的情况,p是旧的元素
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { // p旧元素的下一个元素
p.next = newNode(hash, key, value, null); // 尾插法
// TREEIFY_THRESHOLD = 8,当超过这个长度时将变成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 下一个元素与本元素比较
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; // 继续考虑下一个元素
}
}
// 替换value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

LinkedHashMap

内部单元为Entry,继承自HashMap中的Node

1
2
3
4
5
6
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}

Map中的常用方法

  • Object put(Object key, object value)
  • void putAll(Map m)
  • Object remove(Object key)
  • void clear()

  • Object get(Object key)
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • int size()
  • boolean isEmpty()
  • boolean equals(Object obj)

通过迭代器遍历键与值

  • Set keySet()
  • Collection values()
  • Set entrySet()

Properties(继承自Hashtable)

key和value都是String,通常用于配置文件xxx.properties。使用文件流操作。

Collections工具类

常用方法:

  • reverse(List)
  • shuffle(List)
  • sort(List)
  • sort(List, Comparator)
  • swap(List, int, int)
  • Object max(Collection)
  • Object max(Collection, Comparator)
  • int frequency(Collection, Object)
  • void copy(List destination, List src):Arrays.toList(new Object[src.size()])
  • boolean replaceAll(List, oldVal, newVal)
  • synchronizedXxx()

泛型

类型泛化,默认为Objec类型

使用

1
2
3
ArrayList<Integer> arr = new ArrayList<>();
Map<String, Integer> map = new Map<>();
Set<Map.Entry<String, Integer>> set = new Set<>()

自定义

1
2
3
public Class MyObject<T>{
T myObject;
}

注意点:

  1. 多个泛型参数用逗号隔开
  2. 构造器不需要带泛型声明
  3. 不同泛型引用不能相互赋值
  4. 静态方法中不能使用类的泛型
  5. 不能使用new T()
  6. 静态方法可以是泛型方法

泛型方法:具有泛型的结构,与类的泛型没有任何关系

1
2
3
4
// <E> 说明了他是泛型方法
public static <E> void printList(E e){
System.out.println(e);
}

通配符?的使用

1
2
3
List<String> = new List<>();
List<Object> = new List<>();
List<?> = new List<>(); // 公用父类,拒接写入(add),允许读(get)

有限制的通配符:

  • <? extends SuperClass>: SuperClass的子类或本身才可以, 往下找,add(Object)
  • <? super SuperClass>:SuperClass的父类或本身才可以,往上找,add(Person往下)

文件

File类

不涉及到文件内容,仅存储文件的路径

1
2
3
File file1 = new File("hello.txt");  // 相对于当前的项目路径下
File file2 = new File("D:\\windows\\program\\hello.txt");
File file3 = new File("D:\\windows", "program");

常用方法:

  • public String getAbsolutePath()
  • public String getPath()
  • public String getName()
  • public String getParent()
  • public long length()
  • public long lastModified()
  • boolean renameTo(File2 file2)
  • String[] list():返回路径下的文件名
  • File[] listFile():返回文件对象

  • boolean isDirectory()
  • boolean isFile()
  • boolean exists()
  • boolean canRead()
  • boolean canWrite()
  • boolean isHidden()

IO流

  • 字节流:OutputStream, InputStream
  • 字符流:Writer, Reader
  • 转换流:InputStreamReader, OutputStreamWriter

字符流读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args){
FileReader fr = null;
try {
// 实例化File类对象
File f = new File("src/ind/IOTest/hello_three.txt");
// 提供具体的流
fr = new FileReader(f);

int data = fr.read(); // 读入一个字符
while (data != -1){ // 返回-1说明到达文件末尾
System.out.println(data+" "+(char)data);
data = fr.read();

}
}catch (IOException e){
e.printStackTrace();

}finally {
if (fr!=null)
{
try {
fr.close(); // 关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

字节流使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void streamTest(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File f = new File("src/ind/IOTest/头像.jpg");
File f2 = new File("src/ind/IOTest/头像copy.jpg");
fis = new FileInputStream(f);
fos = new FileOutputStream(f2);
byte[] bcBuffer = new byte[5];
int len;
while ((len = fis.read(bcBuffer)) != -1){
fos.write(bcBuffer);
}

}catch (IOException e){
e.printStackTrace();
}finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

转换流:

  1. 将字节流转为字符流。—-解码过程
  2. 再将字符流转为字节流。—-编码过程

标准输入输出流:System.inSystem.out
打印流:PrintStream
数据流:DataInputStream

对象流(序列化与反序列化)

  • ObjectInputStream
  • ObjectOutputStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("hello!"));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

自定义类可序列化的要求:

  • 实现java.io.Serializable接口
  • 定义序列版本号属性:private static final long serialVersionUID = 10L;:解决序列化与反序列化过程中,版本不一致的问题
  • 其内部所有属性也必须是可序列化的

随机存取文件流(RandomAccessFile类)

网络编程

服务器端例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TCPServer {
public static void main(String[] args) {
ServerSocket ss = null;
Socket s = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
ss = new ServerSocket(8899);
s = ss.accept();
is = s.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[5];
int len;
while ((len=is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null){
is.close();
}
if (s != null){
s.close();
}
if (ss != null){
ss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

客户端例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TCPClient {
public static void main(String[] args) {
Socket s = null;
OutputStream os = null;
try {
InetAddress ia = InetAddress.getByName("127.0.0.1");
s = new Socket(ia, 8899);
os = s.getOutputStream();
os.write("hello! this is client.".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (s != null) {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

反射(动态性)

反射的主要且重要的特点:动态性。运行前存在变数,运行后才确定结果。

反射示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person p = new Person("Tom", 26);
System.out.println(p);
// 反射的使用
Class clazz = Person.class;
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Jack", 12);
Person p1 = (Person) obj;
System.out.println(obj.toString());
// 访问非私有属性
Field nameFiled = clazz.getDeclaredField("name");
nameFiled.set(p1, "Lily");
System.out.println(p1);
// 访问非私有方法
Method method = clazz.getDeclaredMethod("list"); // 调用public方法
method.invoke(p1);
//---------------------------------
// 调用私有成员
Method method1 = clazz.getDeclaredMethod("show");
method1.setAccessible(true);
double res = (double) method1.invoke(p1); // 接受返回值Object转型
System.out.println(res);
}
}

class Person {
String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

private double show(){
System.out.println("this is person.");
return 1.1;
}
public void list(){
System.out.println("list: 1, 2, 3");
}
}

类的加载过程

  1. javac命令编译java源文件,生成字节码文件(.class结尾)
  2. 使用java命令对某个字节码文件解释执行。相当于将某个类文件加载打内存中去。称为运行类,并作为Class类的一个实例。

如何获取Class实例

  • Class<Person> clazz = Person.class;
  • 1
    2
    Person p = new Person();
    Class clazz = p.getClass();
  • Class clazz = Class.forName("ind.Reflection.Person");

  • 1
    2
    ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
    Class clazz3 = classLoader.loadClass("ind.Reflection.Person");

加载过程(类加载器ClassLoader):

  • 加载:将class字节文件加载到内存中去,将静态数据转换为方法区的运行时数据结构
  • 链接:为类变量分配内存,将常量替换为地址引用
  • 初始化:执行类构造器\()方法的过程

加载器:

  • 系统类加载器AppClassLoader
  • 拓展类加载器ExtClassLoader
  • 引导类加载器(无法获取)

使用类加载器加载配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void testA() throws IOException {
Properties pro = new Properties();
FileInputStream fis = new FileInputStream("src/ind/Reflection/jdbc.properties");
pro.load(fis);

String name = pro.getProperty("user");
String pwd = pro.getProperty("password");
System.out.println(name+" "+pwd);
}

public static void testB() throws IOException {
Properties pro = new Properties();
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("ind/Reflection/jdbc.properties"); // 默认在src下
pro.load(is);
String name = pro.getProperty("user");
String pwd = pro.getProperty("password");
System.out.println(name+" "+pwd);
}

创建运行时类的对象

本文标题:Java学习笔记

文章作者:SkecisAI

发布时间:2020年07月23日 - 16:17:21

最后更新:2020年10月22日 - 16:42:46

原始链接:http://www.skecis.top/2020/07/23/java学习笔记/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感谢你的支持,希望本文能助你一臂之力。