此文章为作者基于浙江大学mooc中的java基础和面向对象的相关学习而写,

参考书籍《Java编程思想》

一切内容均来自于个人,以下所谓“差异”,均比较与C语言

Java基础

Java中的大部分内容和c语言极其相似,以下列出一些不同点

基本数据类型差异

整数类型 byte 一个字节
short 两个字节
int 四个字节
long 八个字节
浮点类型 float与double 四字节与八字节(同c语言)
字符类型 char 两个字节,‘\u0000’~‘\uffff’,支持汉字
布尔类型 boolean 一个字节,true false

Java也具有null类型

字面值常量差异

0开头表示八进制

0B/0b表示二进制

0X/0x表示十六进制

同时支持_分隔数字,便于分清数字位数(类似于python)

字面值常量整数默认为int,长整型long在数字末尾需要添加L

字面值常量浮点数默认为double,浮点型float在数字末尾需要添加F或f

boolean为逻辑数字类型,只有true和false两种字面值,并且不对应于任何整数值1和0(这一点与c语言不同)

变量声明差异

大部分和c语言一样,需要提前声明变量,并且需要进行初始化

java的变量如果不进行初始化,则会默认设为0或者false

Java11后支持局部变量类型推定,使用var进行声明变量,变量类型未定,根据上下文自动推定类型

运算提醒

与c语言相同,对于&&||运算都具有“短路”现象,只有左操作数无法决定最终的值时才会计算右操作数

>>>代表无符号右移,高位补0

instanceof测试某个对象是否为指定类的对象实例,返回boolean,类似于python

整数计算的时候默认为int型,即使所有参与运算的数据类型都低于int,除非有long,则上升类型为long

带有浮点数运算默认为float型,除非有double,则上升类型为double

控制语句差异

除了c语言一样的控制语句之外,java还有一些特殊的控制语句形式

1
2
3
4
5
6
7
static void rangeOf(int k){
switch(k){
case 1,2,3 ->System.out.println("Ranking one");
case 4,5,6,7 ->System.out.println("Ranking two");
default ->System.out.println("Ranking three");
}
}

支持多种条件写在同一行,同时不需要冒号,使用->,不需要break语句跳出(在JDK14版本之后)

增强型for循环,主要用于枚举、数组和集合对象的元素遍历

Foreach用法,用于数组和容器

for(<类型><变量>:<集合对象>)声明类型需要与集合对象相兼容

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String []args) {
int[] num = new int[100];
for (int i = 0; i < 100; i++) {
num[i] = i;
}
for (int k : num) {
System.out.println(k);
}
}
}

移位操作符

移位仅针对int,long使用,对于byte, char, short,在移位前都会转换成同数字大小的int

位操作都是针对二进制补码操作

<<左移,低位补0

>>有符号右移,如果是正数,高位全部补0,如果是负数,高位全部补1

>>>无符号右移,不论正负,全部补0

java对于移位操作的数值有一定限制,对于int类型的操作数只考虑低五位,long类型只考虑低六位

比如说30>>32等价于30>>0,有一个类似于取模32的过程,对于long就有个取模64的过程

static详解

static大多数用于类的成员变量或者成员方法,少数用于代码块提升性能,static不能修饰局部变量

  • static修饰的成员变量或者方法属于,因此生命周期和类相同
  • 普通成员变量或者方法属于对象
  • 静态方法不能调用非静态成员变量,会报错
  • 非静态方法可以调用非静态方法和静态方法,可以调用非静态成员和静态成员

static方法称为静态方法,不依赖于任何对象就能使用,因此也不具备this的使用,其不依赖于任何对象,没有对象,也就没有this

静态方法中不能使用非静态成员变量和非静态方法,因为后者都是需要依附于对象才能被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
static String name = "soy";
String address = "BUAA";

Main() {

}

void test1() {
System.out.println(name);
System.out.println(address);
test2();
}

static void test2() {
System.out.println(name);
System.out.println(address);//error
test1();//error
}
}

这个示例的第17行,在静态的方法内调用了非静态成员变量address,系统报错

第18行,在静态方法内部调用了非静态方法test1(),系统报错

但是对于非静态方法,其可以随意调用静态变量和方法

静态方法可以不创建对象就调用,最常见的就是main方法是静态的

  • 静态变量被所有对象共享,内存只有一份,不同对象都可以修改该静态变量
  • 非静态变量相互独立,在对象创建的时候进行初始化,有多个副本互不影响

在类被加载的时候,static代码块就按照顺序进行初始化了

静态的加载顺序是从上往下依次执行,静态变量和静态代码块视作同一类,从上往下执行

静态代码块

静态代码块里面定义的变量都是局部变量,只在本块中有效

初始化与清理

构造器初始化

在类里面使用和类名相同的方法作为构造函数,可以有多个构造函数,区别在于参数不同

构造器没有返回值

如果类没有构造器,系统会自动为你创造一个默认构造器

方法重载

同一个类里面支持多个同名方法,区别在于参数不同,方法重载在涉及到数据的类型转换时可能有一些问题

传入的参数类型低于要求的类型,就会自动提升参数类型

传入的参数类型高于要求的类型,就会自动降低到参数对应的类型

this

this表示对当前对象的引用,可以作为返回值,返回对当前对象的引用

构造器之间可以相互调用,但是一次只能调用一个构造器,使用this调用

只有构造器能调用构造器,其它方法不能调用构造器

变量初始化顺序

类的内部,变量的初始化顺序取决于其定义的先后顺序,但是不论他们处于任何位置,他们都会在构造器和任何方法调用之前完成初始化

多个类

一个.java文件允许有多个class,但是一般只有一个classpublic修饰作为主类,主类的名字和.java文件名字相同

类与类的关系

  • 关联:是一种has的关系
    • 一对一
    • 一对多
  • 依赖:比has关系较弱,类之间的调用关系
  • 聚集:整体和部分之间的联系
    • 组合
  • 泛化:类之间的继承关系
  • 实现:类与接口的关系

封装

  • 将对象的属性和方法看成一个整体
  • 就信息进行隐藏,赋予一定的权限

一般情况下,给所有的成员变量加入private权限限制,写相关属性的get和set方法,将这些方法设置为public,从而通过方法访问私有属性

  • private仅仅在本类中可见,提升数据的安全性
  • public权限最低,其它类中也可以访问,减少代码冗余
  • protected向子类和同一个包中的类公开
  • 默认权限,向同一个包中的类公开

protected修饰规则详解:

  1. 基类(父类)的protected成员(包括成员变量和成员方法)对本包内可见,并且对子类可见;
  2. 若子类与基类(父类)不在同一包中,那么在子类中,只有子类实例可以访问其从基类继承而来的protected方法,而在子类中不能访问基类实例(对象)(所调用)的protected方法。
  3. 不论是否在一个包内,父类中可以访问子类实例(对象)继承的父类protected修饰的方法。(子父类访问权限特点:父类访问域大于子类)
  4. 若子类与基类(父类)不在同一包中,子类只能在自己的类(域)中访问父类继承而来的protected成员,无法访问别的子类实例(即便同父类的亲兄弟)所继承的protected修饰的方法。
  5. 若子类与基类(父类)不在同一包中,父类中不可以使用子类实例调用(父类中没有)子类中特有的(自己的)protected修饰的成员。(毕竟没有满足同一包内和继承获得protected成员的关系)

封装的单实例模式

“懒汉式”单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
//静态的。保留自身的引用。
private static Singleton test = null;

//必须是私有的构造函数
private Singleton() {
}

//公共的静态的方法。
public static Singleton getInstance() {
if (test == null) {
test = new Singleton();
}
return test;
}
}

• 是否 Lazy 初始化:是,只有需要的时候才会初始化

• 是否多线程安全:否

• 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

• 特点这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作只有一个实例,共享的数据,便于频繁查询修改

“饿汉式”单例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
//静态的,保留自身的引用,类加载时就初始化
private static Singleton test = new Singleton();

//必须是私有的构造函数
private Singleton() {
}

//公共的静态的方法。
public static Singleton getInstance() {
return test;
}
}

• 是否 Lazy 初始化:否,不论需不需要,类加载的时候就直接创建实例初始化

• 是否多线程安全:是

• 优点:没有加锁,执行效率会提高。

• 缺点:类加载时就初始化,容易产生垃圾对象,浪费内存。

• 特点:它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化。

常用集合框架设计

List列表

有序性,可重复性

列表的三种类型,E为每个元素的类型

  • List<E> list = new ArrayList<>(),基于数组实现的动态数组,增删慢,查询快
  • List<E> list = new LinkedList<>(),基于链表实现的双向链表,增删快,查询慢
  • List<E> list = new Vector<>(),类似于ArrayList,多线程安全,性能较差

常用方法:

  • list.size(),返回元素个数
  • list.add({int index ,}E element),可选参数index,默认在列表尾部添加元素
  • list.get(int index),获取对应下标的元素
  • list.set(int index, E element),设置对应下标的值
  • list.remove(int index),移除指定下标的元素
  • list.isEmpty(),检查列表是否为空
  • list.contains(Object o),是否包含某元素
  • list.indexOf(Object o),某个元素第一次出现的下标
  • list.subList(int fromIndex,int toIndex),获取子列表
  • 待补充

列表初始化

在已知列表即将被初始化后的每个元素类型时,使用一些已有的来构造列表

获取已有的列表的元素List<String> newList = new ArrayList<>(existingList)

比如常用的List<Map.Entry<String, Name>> list = new ArrayList<>(map.entrySet())

map.entrySet()就是返回一个由键值对构成的集合

Map键值对

无序性,键的不可重复性

四种类型,E1为键的类型,E2为值的类型

  • Map<E1, E2> map = new HashMap<>(),基于哈希表实现,提供快速查找,插入,删除
  • Map<E1, E2> map = new TreeMap<>(),基于红黑树实现,提供有序键值对,按照键的自然顺序或自定义顺序排序
  • Map<E1, E2> map = new LinkedHashMap<>(),基于哈希表和链表实现,保持了插入顺序或者访问顺序,允许迭代时按顺序访问
  • 待添加

常用方法:

  • map.put(E1 key, E2 value),向表中添加键值对
  • map.containsKey(E1 key),查询是否包含“键”
  • map.get(E1 key),返回对应键的值
  • map.remove(E1 key),删除对应键的值
  • map.size(),获取map的大小
  • map.isEmpty(),检查map是否为空
  • map.keySet(),获取map的键的集合
  • map.values(),获取map的值的集合
  • map.entrySet(),获取map的键值对的集合,常常用于返回给列表进行排序
  • map.clear(),清空所有键值对
  • 待添加

HashSet集合

实现了Set接口,存储一组唯一元素,不允许元素的重复性,无序,基于哈希表实现

  • hashSet.add(E e),添加元素,如果已经存在,不会重复添加
  • hashSet.remove(E e),移除元素
  • hashSet.contains(E e),检查是否包含
  • hashSet.size(),元素数量,集合大小
  • hashSet.isEmpty()是否为空
  • hashSet.clear(),清空集合

Iterator迭代器

Iterator是一个接口,用于遍历集合(如List, Set, Map)中的元素。

Iterator提供了标准的迭代方式,不用知道底层逻辑,允许顺序访问集合中的元素

调用集合的iterator()方法获取实例

1
2
3
4
5
6
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String element = iterator.next();
//对element进行操作
}

由于Java5之后有增强型的for-each,显式的iterator不再使用

继承

父类的属性和方法可以应用于子类,代码复用性高,可以轻松自定义子类

  • 父类superclass,子类subclass
  • 单继承与多继承,多继承拥有多个父类

抽象类

1
2
3
4
5
abstract class Animal {
public stract sleep();
public stract eat();//抽象类不能写{},必须直接分号结尾
//包含抽象方法,必须写成抽象类,抽象类不一定要写抽象方法
}

子类拥有父类所有的属性和方法,包括私有属性,但是无法直接访问私有属性,需要父类的get()方法间接访问

子类无法继承父类的构造方法,构造方法无法被继承,子类需要定义自身的构造方法

  • java不支持类的多继承,但是支持接口的多继承,一个类只能有一个直接的父类,使用extends连接
  • 子类调用父类都需要super(),super().方法名
  • 将基类数据成员定义为private,使用相应的构造函数去访问,保证封装性,降低效率
  • 基类仅仅对派生类提供服务时,可以设置为protected,如果有除了派生类以外的无关类,则建议private
  • 派生类可以自动向基类进行类型转换,向上映射会丢失子类的独有的方法,但是如果是继承的方法,那么调用时依然调用子类重写的方法

运行时类型:由该变量指向的对象类型决定

编译时类型:由该变量声明时的类型决定

覆盖:子类重写父类的方法,方法名和参数类型完全一样,覆盖是对于实例方法而言的

方法不能交叉覆盖:子类实例方法不能覆盖父类静态方法,子类静态方法也不能覆盖父类实例方法

隐藏:父类和子类具有相同的名字的属性或者方法(方法隐藏仅有一种可能,父类和子类都是静态方法),父类的同名属性或者方法在形式上不见了,实际还是存在的

  1. 当发生隐藏时,声明类型是什么类,就调用对应类的属性或者方法,而不会有动态绑定(运行时)
  2. 属性只能被隐藏,无法被覆盖
  3. 变量允许交叉隐藏,即非静态的隐藏静态的,静态的隐藏非静态的

隐藏与覆盖的区别:

  1. 被隐藏的属性,在子类向上转型成父类时后,访问的是父类中的属性,
  2. 在没有转换时,子类访问父类的属性需要super关键字
  3. 被覆盖的方法,在子类向上转型成父类后,调用的还是子类自身的方法,如果想要访问父类还是可以用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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Test {
public static void main(String[] args) {
Circle circle = new Circle();//本类引用指向本类对象
Shape shape = new Circle();//父类引用指向子类对象(会有隐藏和覆盖)

System.out.println(circle.name);
circle.printType();
circle.printName();
//以上都是调用Circle类的方法和引用

System.out.println(shape.name);//调用父类被隐藏的name属性
shape.printType();//调用子类printType的方法
shape.printName();//调用父类隐藏的printName方法
}
}

class Shape {
public String name = "shape";

public Shape(){
System.out.println("shape constructor");
}

public void printType() {
System.out.println("this is shape");
}

public static void printName() {
System.out.println("shape");
}
}

class Circle extends Shape {
public String name = "circle"; //父类属性被隐藏

public Circle() {
System.out.println("circle constructor");
}

//对父类实例方法的覆盖
public void printType() {
System.out.println("this is circle");
}

//对父类静态方法的隐藏
public static void printName() {
System.out.println("circle");
}
}

output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
shape constructor
circle constructor
//普通对象的创建
shape constructor
circle constructor
//普通对象的创建
circle
this is circle
circle
//普通对象的访问
shape
this is circle
shape
//类型向上强转,属性被隐藏,type方法被重写覆盖,name方法被隐藏
//隐藏的属性,调用声明时类型,输出shape
//覆盖的方法,调用子类的方法,输出this is circle
//被隐藏的方法,调用声明时类型,输出shape

final用法

  1. final修饰变量,final变量被赋了初值就无法再改变
  2. final修饰的方法不可被重写,比如某个父类的方法加入final,那么子类无法再重写该方法
  3. final修饰的类无法被继承

多态

静多态:编译时多态,静态联编,静绑定。方法重载,方法隐藏

方法重载:方法同名,参数列表不同

方法重载特例:允许不同访问权限修饰,允许不同返回值,构造方法和静态方法也可以重载

动多态:运行时多态,动态联编,动绑定。条件:继承,覆盖,向上转型

有继承的情况,继承中必须有方法覆盖(override),通过父类调用被覆盖的方法

方法覆盖:方法名,参数列表一致,子类方法不能缩小访问权限

方法覆盖特例:私有方法,静态方法不能被覆盖,如果子类有同名方法,那就是执行方法隐藏(实际还是存在),final方法不允许覆盖

抽象

关键字abstract修饰方法和类,表示“尚未实现”

抽象类:通常作为其它类的super类,父类

抽象方法:没有方法体,直接以分号结束,抽象类可以没有抽象方法,有抽象方法必须为抽象类

抽象类的引用:抽象类虽然不能实例化,但是可以引用,作为向上转型的桥梁,将子类实例传递给抽象类的引用实现多态

抽象方法的权限:抽象方法不能被private, final, static修饰,因为需要重写覆盖(override),还有访问相关的问题

接口

Interface关键字说明“接口”,接口中可以定义常量和方法,都是默认public abstract

接口和类的区别:

  • 接口不能用于实例化对象
  • 接口没有构造方法
  • 接口所有的方法必须是public abstract,一般建议写上,不写也是默认这种
  • 接口没有成员变量,其变量都是public static final
  • 接口需要被类实现
  • 接口支持多继承,也就是一个类允许实现多个接口
  • JDK1.8之后,接口允许包含“默认方法”,使用default修饰
  • 接口之间允许继承,越继承该接口需要实现的方法一般就越多

class 类名 implements 接口1[,接口2,接口3…]

  • 在“实现类”实现接口时,类要实现接口中的所有方法,否则必须声明为抽象类
  • 类在实现/重写接口的方法时,要保持一致的方法名和参数,返回值可以选择兼容的,权限修饰必须为public,不能缩小修饰范围,否则编译报错
  • 一个类继承多个接口时,多个接口之间的同名方法的返回值需要互相兼容,否则无法同时实现多个接口中的方法,造成编译报错
  • 一个类继承多个接口时,多个接口之间的变量不要同名,否则在实现类里面调用接口的常量时,编译器无法确定调用的是哪一个接口的常量。当然也可以在调用常量的时候说明调用的是哪一个接口的常量

内部类

成员内部类

成员内部类是在一个类的内部定义的类,它与外部类之间有特殊关系,因为内部类实例依赖于外部类的实例

  • 定义方式:成员内部类是在外部类中定义的,可以像定义其它成员变量一样定义内部类
  • 访问:成员内部类可以访问外部类的成员,包括私有成员
  • 初始化:成员内部类的实例通常需要外部类的实例来初始化
1
2
3
4
5
6
7
8
9
public class OuterClass {
private int outerVar;

public class InnerClass {
public void doSomething() {
outerVar = 10; // 内部类可以访问外部类的成员
}
}
}

局部内部类

局部内部类是在方法内定义的类,它的作用域仅限于它的方法。局部内部类通常用于需要实现某个接口或者继承某个类的情况

1
2
3
4
5
6
7
public class OuterClass {
public void someMethod() {
class LocalInnerClass {
// 局部内部类的定义
}
}
}

匿名内部类

匿名内部类是一种没有显式名称的内部类,通常用于创建临时对象,实现接口或者继承类的情况。通常在创建对象的地方创建,并且可以包含类的定义和初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface MyInterface {
void doSomething();
}

public class OuterClass {
public void someMethod() {
MyInterface anonymous = new MyInterface() {
public void doSomething() {
// 实现接口方法
}
};
}
}

内部类的初始化过程: 内部类的初始化与外部类的初始化过程是相互独立的。在初始化内部类之前,外部类必须首先初始化。内部类的初始化可以在需要时进行,它不会随着外部类的加载而立即发生。

内部类的调用情况: 内部类可以通过外部类的实例来访问,也可以通过创建内部类的实例来访问。外部类可以访问内部类的成员,前提是内部类的成员具有适当的可见性修饰符(例如,publicprotecteddefaultprivate)。

内部类的多重嵌套: Java支持内部类的多重嵌套,即一个内部类可以包含另一个内部类,可以嵌套多层次。这种多重嵌套通常用于创建复杂的数据结构或实现某些设计模式。

1
2
3
4
5
6
7
public class OuterClass {
public class InnerClass1 {
public class InnerClass2 {
// 多重嵌套的内部类
}
}
}

还可以为内部类写一个私有的返回外部类对象的方法

1
2
3
private xxxxxxx(外部类名字) getOuterClass(){
return xxxxxxx(外部类名字).this.
}

内部类的初始化过程

  1. 加载外部类: 在使用内部类之前,首先要加载外部类。这包括查找外部类的字节码文件并加载它,但不会初始化内部类。
  2. 初始化外部类: 如果外部类有静态成员,静态初始化块或静态方法,它们将在加载外部类时进行初始化。这不包括内部类的初始化。
  3. 创建外部类的实例: 如果您需要使用非静态内部类,您需要首先创建外部类的实例。内部类通常与外部类实例相关联。
  4. 加载内部类: 内部类的加载通常是在创建外部类的实例后才发生的。加载内部类时,与加载外部类一样,Java会查找内部类的字节码文件并加载它。内部类的字节码文件通常以外部类名$内部类名.class的形式存在。
  5. 初始化内部类: 内部类的初始化是在加载内部类后,首次使用它之前进行的。这包括静态初始化块、静态成员和构造函数的执行。

需要注意的是,内部类的初始化仅在需要使用内部类时才会发生。这意味着如果您从未实例化外部类或从未使用内部类,那么内部类可能永远不会初始化。

下面是一个示例代码,展示了内部类初始化的过程:

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
javaCopy codepublic class OuterClass {
static {
System.out.println("OuterClass static block");
}

public OuterClass() {
System.out.println("OuterClass constructor");
}

public class InnerClass {
static {
System.out.println("InnerClass static block");
}

public InnerClass() {
System.out.println("InnerClass constructor");
}
}

public static void main(String[] args) {
OuterClass outer = new OuterClass();
System.out.println("Creating OuterClass instance");

OuterClass.InnerClass inner = outer.new InnerClass();
System.out.println("Creating InnerClass instance");
}
}

泛型

泛型方法

定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

java 中泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • - 表示不确定的 java 类型

泛型标记符是一种习惯上的标准,便于区别实际调用类型

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
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}

public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组

System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组

System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}

整型数组元素为:
1 2 3 4 5

双精度型数组元素为:
1.1 2.2 3.3 4.4

字符型数组元素为:
H E L L O

类型参数是可以有界的

  • 上界:<T extends balabla>,表示T代表的只能是balabala及其子类,同时可以声明多个上界,用&分隔,不建议,有限制规则
  • 下界:<T super balabala>,表示T代表的只能是balabala及其超类
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 MaximumTest
{
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
{
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main( String args[] )
{
System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ) );

System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );

System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear",
"apple", "orange", maximum( "pear", "apple", "orange" ) );
}
}

3, 45 中最大的数为 5

6.6, 8.87.7 中最大的数为 8.8

pear, apple 和 orange 中最大的数为 pear

类型通配符?代表具体的类型参数。例如 List<?> 在逻辑上是 List<String>,List<Integer> 等所有 List<具体类型实参> 的父类。

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
import java.util.*;

public class GenericTest {

public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();

name.add("icon");
age.add(18);
number.add(314);

getData(name);
getData(age);
getData(number);

}

public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}

data :icon
data :18
data :314

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。class <T>

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Box<T> {

private T t;

public void add(T t) {
this.t = t;
}

public T get() {
return t;
}

public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();

integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程"));

System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}

文件IO

File file = new File(Path),创建新的文件对象,文件对象有很多方法

File.createNewFile,需要有对应的目录,如果父目录不存在就会报错

File.getParentFile(),获取父目录文件,如果文件不存在就需要创建父目录

创建父目录有两种

File.mkdir()创建单级目录

File.mkdirs()创建多级父目录,是一个递归过程

  • File.exists()
  • File.isDirectory()返回是否是目录
  • File.isFile()是否是文件
  • File.delete(),如果是文件就直接删除,如果是空目录就直接删除,否则需要递归删除文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void deleteDirectory(File directory) {
if (directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
// 递归删除子目录
deleteDirectory(file);
} else {
file.delete(); // 删除文件
}
}
}
}
directory.delete(); // 删除空目录
}

文件复制

首先是普通的文件复制

给定源文件路径和目标文件路径,拷贝内容

1
2
3
4
5
6
7
8
9
10
11
public static void copyFile(String sourceFile,String targetFile) throws IOException {
File file = new File(targetFile);
file.createNewFile();
BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile));
String line = null;
while((line = reader.readLine())!=null){
writer.write(line);
writer.newLine();
}
}

然后是目录文件的递归复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void copyDirectory(String sourceDir,String targetDir) throws IOException{
File file1 = new File(sourceDir);
File file2 = new File(targetDir,file1.getName());
if(file1.isDirectory()){
File files[]= file1.listFiles();
file2.mkdir();
for(File fi:files){
if(fi.isDirectory()){
copyDirectory(fi.toString(),file2.toString());
}else{//是文件
File file3 = new File(file2.toString(),fi.getName());
copyFile(fi.toString(),file3.toString());
}
}
}else{//是文件
copyFile(file1.toString(),file2.toString());
}
}

字节流Byte Steams

InputStreamOutputStream是所有字节流的基类

  1. FileInputStream从文件中读取字节
  2. FileOutputStream将字节写入文件
1
2
3
4
5
FileInputStream fis = new FileInputStream("input.txt");
int data = fis.read();
FileOutputStream fos = new FileOutputStream("output.txt");
fos.write(data);

  1. BufferedInputStreamBufferedOutputStream提供缓冲,提升效率
1
2
3
InputStream is = new BufferedInputStream(new FileInputStream("input.txt"));
OutputStream os = new BufferedOutputStream(new FileOutputStream("output.txt"));

  1. ByteArrayInputStream从字节数组中读取数据
  2. ByteArrayOtputStream将数据写入字节数组
1
2
3
4
byte[] data = { 65, 66, 67 };
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();

字符流Character Streams

ReaderWriter是所有字符流的基类

  1. FileReader从文件中读取字符
  2. FileWriter将字符写入文件
1
2
3
4
FileReader fr = new FileReader("input.txt");
int data = fr.read();
FileWriter fw = new FileWriter("output.txt");
fw.write(data);
  1. BufferedReaderBufferedWriter提供了缓冲,提升xiaol
1
2
Reader reader = new BufferedReader(new FileReader("input.txt"));
Writer writer = new BufferedWriter(new FileWriter("output.txt"));
  1. CharArrayReader从字符数组里读取数据
  2. CharArrayWriter将数据写入字符数组
1
2
3
char[] data = { 'A', 'B', 'C' };
CharArrayReader car = new CharArrayReader(data);
CharArrayWriter caw = new CharArrayWriter();

序列化操作

ObjectOutputStream将对象序列化成字节流

1
2
3
4
5
6
7
8
FileOutputStream fileOutputStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
FavoriteCommodityList favoriteCommodityList = Variable.user.getFavoriteCommodityList();
for (FavoriteCommodity m : favoriteCommodityList.getList()) {
objectOutputStream.writeObject(m);//将对象转换成字节序列写入文件,但是这个对象仍然存在,只是备份了一份成字节
}
fileOutputStream.close();
objectOutputStream.close();

ObjectInputStream将字节流重新返回成对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FileInputStream fileInputStream = new FileInputStream(path);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
while(true){
try{
Object obj = objectInputStream.readObject();//将字节流重新转化为对象,用Object
if(obj instanceof FavoriteCommodity){
FavoriteCommodity item = (FavoriteCommodity) obj;//将对象实例转型
Variable.user.getFavoriteCommodityList().addCommodity(item);
}
}catch (IOException e){
break;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
fileInputStream.close();
objectInputStream.close();