Java基础知识
- 一些基础概念
- 垃圾回收机制
- 时间和日期
- 常用工具类和方法
- import
- 数组
- 数组与集合的比较
- String
- 集合
- 类
- Static
- final
- 抽象类
- 接口 interface
- 内部类
- 泛型
- IO流
- Java 8
- Java 9
- 类的生命周期
- 对象的生命周期
- 反射
- 递归
一些基础概念
在JDK安装目录下可以找到一个src.zip
压缩文件,该文件中包含了Java基础类库的所有源文件。
面向对象
类描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类就是一个数据类型。我们在进行面向对象的程序设计中,实际上进行的是创建新的数据类型;所有的面向对象设计语言都使用class这个关键字来表示数据类型。
JDK安装和环境变量配置
JDK (Java SE Development Kit),Java标准开发包,它包含了:
- Java编译器
- Java运行时环境(JRE)
- 常用的Java类库
JRE(Java Runtime Environment),它是运行Java程序的必需条件:
- 核心虚拟机(JVM)
- 类加载器
- 字节码校验器
- 大量基础类库
- … (运行Java程序的其他环境支持)
Java EE SDK (Software Development Kit)软件开发工具包 :(它包含了JDK)包含特定的软件包,软件框架/硬件平台,操作系统等建立应用的开发工具的集合。
暂时用不到。做Java Web开发用JDK就好。
安装JDK:
- 在Oracle下载安装包
- 运行安装程序
- 选择要安装的组件
- Devepment Tools:是JDK核心。实际上它已经包含了运行Java程序的JRE,该JRE会安装在JDK目录的子目录下。
- Source Code:核心类库的源代码
- Public JRE:公共JRE(可选)。独立的JRE系统。
JDK安装路径下的
src.zip
文件存放了Java所有核心类库的源代码。
API文档下载与查看:
环境变量:
配置Path环境变量:
PATH
环境变量是一系列路径,操作系统将在这些路径中依次查找命令。
在PATH
变量下追加自己的jdk安装路径下的bin目录的路径...\Java\jdk*.*.*\bin
注意: PATH
环境变量的配置只是为了 能够在命令行下执行 java
javac
等命令而已,如果使用IDEA开发java则还需要在IDEA中配置或为其添加其他环境变量。
开发环境可能会用到的环境变量:windows
JAVA_HOME
:JAVA_HOME:D:\Program Files\java\jdk1.8
``CLASSPATH
:
CLASSPATH:%java_home%\lib`
CLASSPATH
环境变量的作用:显式指定Java类(.class文件)的搜索路径。1.4版本之前的JDK才需要设置,现在JRE默认已经能够自动搜寻当前路径.
和JDK下的lib
文件夹;如果你显式设置了CLASSPATH
,则会跳过默认值,所以记得将.
包含在里面。不推荐设置CLASSPATH
见《疯狂Java讲义(第3版)》1.5.4
Java源文件命名规则
- Java源文件的后缀必须是
.java
- Java源文件的文件名必须与其内部的
public
类名相同
变量
变量的命名:
区分大小写
在Java中,任何对象变量的值都是存储在另外一个地方的对象的引用。new操作符的返回值也是一个引用。
变量的作用域
略
补充:
- 实例变量:声明在一个类中,但在方法和语句块之外声明
- 静态变量(类变量):在类中以static修饰符声明,但必须在方法和语句块之外声明
- 局部变量:在类中某方法中声明
就像引用类型的数组一样,当把Java对象放入数组中时,并不是真正把Java对象放入数组中,而是把对象的引用放入数组中,每个数组元素都是一个引用变量。
变量的初始化
参数传递时栈的变化
java面向对象编程 p97
常量
常量在程序运行时是不能被修改的。
在 Java 中使用 final
关键字来修饰常量。通常使用大写字母表示常量。
编译时常量:对于 final 类型的静态变量,如果在编译时就能计算出变量的取值(这是条件),那么这种变量被看做编译时常量。
基本类型
Java语言提供了8种基本类型。6种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
整型: byte :1字节、short:2字节、int:4字节、long:8字节
浮点型:float :4字节、double:8字节
字符型: char : 2字节
布尔类型:boolean
byte 的中文意思就是:字节
包装类型:
字符char与字节byte
来源字节流与字符流的比较。
一个字节(byte) = 8位,是计算机处理数据的基本单位?
字符型(char)通常用于表示单个的字符,比如'a'
,Java使用 16 位 (两字节) 的Unicode字符集作为编码方式。也就是说字符型要考虑字符集的问题。
TODO:字符集。《疯狂Java讲义》
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
// 输出结果:
abc
虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
引用类型
new关键字
当一个引用类型的变量被声明后,如果没有初始化,那么它不指向任何对象。Java语言用new关键字创建对象。其作用如下:
- 为对象分配内存空间,将对象的实例变量自动初始化为其变量类型的默认值。
- 如果实例变量在声明时被显式初始化,那么就把初始化值附给实例变量。
- 调用构造方法。
- 返回对象的引用。
TODO 补充,内存分配
初始值
new
自增和自减
++i
、--i
:先改变变量值,再使该变量值参与表达式的运算
++i
、--i
:先使该变量值参与表达式的运算,再改变变量值
优先级:
%
%
取余/求模
小 % 大
0 % 正
逻辑判断短路现象
即一旦能够明确无误的确定整个表达式的值,就不会再计算(运算)表达式余下部分。
==和equals方法
对于基本的数值类型==
和!=
比较的是两个变量的值。
对于引用类型变量==
和!=
比较的对象的引用(地址)
如果想比较两个基本类型对象的实际内容,可以使用equals()
方法。另大多数Java类库都实现了equals()
方法,以便我们用来比较对象的内容,而非比较对象的引用。
equals()
方法是Object提供的一个实例方法,默认与==
一样比较对象的引用(地址)。正如上面说的大多数Java类库都重新实现了自己的equals()
方法;那么对于自定义的类,如果想比较该类对象的内容,也需要重写equals()
方法。
重写
equals()
方法时,推荐按照 InteliJ IDEA自动生成的equals()
的逻辑:@Override public boolean equals(Object o) { //判断是否是同一对象 if (this == o) return true; //判断参数是否为null,判断类型是否一致 if (o == null || getClass() != o.getClass()) return false; //强制转换为当前类型 Person that = (Person) o; // 使用Objects.equals替换String的equals,因为前者优化了空指针异常的情况 return age == that.age && Objects.equals(name, that.name); }
//优势是?
Objects.equals();
垃圾回收机制
时间和日期
《Java核心技术卷 I》第4章 4.1.1中有关于这些介绍。
Date表示时间,Calendar表示日期?
表示时间点的Date,表示大家熟悉的日历表示法的GregorianCalendar类。其中GregorianCalendar类扩展了一个更加通用的Calendar类。
Date类只提供了少量的方法用来比较两个时间点。例如before和after方法。(其他方法不推荐使用)
两者之间的转换:
GregorianCalendar -> Date
Date time = calendar.getTime();
Date -> GregorianCalendar
calendar.setTime(new Date());
获取当前日期和时间:
GregorianCalendar d = nwe GregorianCalendar();
//更易使用的方法
GregorianCalendar.getInstance();
常用工具类和方法
检测是否为 null
// 优势是什么?
Objects.isNull();
import
import: import可以向某个Java文件中导入指定包层次下某个类或全部类。
import 语句中的星号(*)只能代表类,不能代表包。
示例:
import lee.Apple; //导入lee包下的单个Apple类
import lee.*; //表明导入lee包下的所有类。而lee包下的sub子包中的类则不会被导入。
import lee.sub.* //必须使用该语句,才能导入lee的子包sub中的类
import static 静态导入:
import static静态导入用来导入静态方法和静态域。而import用于导入类。
静态导入用于导入指定类的单个静态成员变量、静态方法或全部的静态成员变量、静态方法。
示例:Math类中有非常多的静态方法,使用静态导入后使用这些静态方法时便可以不添加类名Math作为前缀。
import导入后,使用导入包中某类的公共成员时,可以省略包名。
而使用import static导入后,使用导入某类的公共静态成员时可以省略类名。
(静态方法和静态域属于某个类;而类属于某个包)
数组
定义数组的两种语法示例:
int[] arrayName;//推荐
int arrayName[];//淘汰
多维数组
Arrays工具类
Arrays类里包含的一下static修饰的方法可以直接操作数组。
- 二分查找
- 复制数组
- 比较两个数组是否相等(长度和元素都相等)
- 对数组排序
- 转换为字符串
另外:Java 8还新增了提高并行能力的对应方法。
数组与集合的比较
给出一个数组和集合:
//定义并初始化一个数组
int [] arr = {1,2,3,4};
//定义一个集合
LinkedList<String> list = new LinkedList<>();
//为集合添加元素
list.add("a");
数组的特点:
[]
: 表示数组- 数组有索引:每一个存储到数组的元素,都会自动的拥有一个编号,从0开始。可以使用
数组名[索引]
的方式访问和修改数组元素 - 可以通过
length
属性(注意:是属性而非方法),获取数组长度:数组名.length
- 长度(大小)固定不变
(对应)集合的特点:
- 有序、可重复的集合有索引,比如List及其子类(LinkedList、 ArrayList)(自己总结)
- 可以通过
size()
方法获取元素个数
由于只有有序可重复的集合才有索引(这只是自己总结的),所以Collection没有提供索引相关的方法。比如:List集合有序,而Set集合无序。LinkedHashSet是有序的,但是没有索引。
打印数组和集合:
//直接打印数组变量(该变量中保存的地址)
System.out.println(arr); // 结果类似: [I@4554617c
//直接打印集合对象
//因为集合的实现类都重写了toString()方法
System.out.println(list); //[11]
//打印数组的方式
System.out.println(Arrays.toString(arr));
获取数组和集合中的元素:
// 定义数组,存储3个元素
int[] arr = new int[3];
//数组索引进行赋值
arr[0] = 5;
arr[1] = 6;
arr[2] = 7;
//输出3个索引上的元素值
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
//定义数组变量arr2,将arr的地址赋值给arr2
int[] arr2 = arr;
arr2[1] = 9;
System.out.println(arr[1]);
对于有序、 可重复集合,可以使用和索引相关的方法比如 get()
等方法来获取对应位置上的元素。
对于无序、 不可重复集合,可以使用Collection的迭代器来遍历集合,从而获取元素。
由于部分集合才有索引,那么Collection作为集合的父类当然不能提供索引相关的方法。
注意: 不能在使用Iterator遍历集合时,对集合执行增删操作。因为Iterator并不能和集合同步(它们是不同的类),它不知道集合已经发生了改变。
String
就是char数组,唯一为其重载了加号。String的构造过程。
见《Java编程思想》
集合
单列集合:
- Collection: 所有 单列集合 的顶层接口,定义了单列集合的共性方法
add(E e) remove(Object o) size() isEmpty() clear() contains(Object o)....
- List: 存取有序,有索引,元素可以重复
-
Vector : 过时 数组结构 查询快,增删慢 线程安全,效率低
-
ArrayList :
数组结构 查询快,增删慢 线程不安全,效率高
-
LinkedList :
链表结构 查询慢,增删快 线程不安全,效率高
-
- Set: 存取无序,无索引,元素不可重复(包含的方法基本与Collection相同)
- HashSet:由哈希表保证元素的唯一性
- LinkedHashSet:存取有序,元素唯一, 由链表保证存取有序,有哈希表保证元素唯一
- TreeSet:由树状结构保证元素的唯一性
- List: 存取有序,有索引,元素可以重复
双列集合
没有索引,没有提供迭代器。
- Map:
- HashMap:
- LinkedHashMap:
- TreeMap:
- HashMap:
如果新添加的键值对的键重复,就会覆盖之前该键的值。
HashSet的底层依赖于HashMap实现(顺序不要错了)。
类
- Java只支持单继承,不支持多继承。
- Java支持多层继承(继承体系)。
访问权限
访问控制的4个级别:
- public:公开级别
- protected:受保护级别,向子类及同一个包中的类公开
- 默认级别:没有访问控制修饰符,向同一个包中的类公开
- private:私有级别,只有类本身可以访问,不对外公开
访问级别 | 访问控制修饰符 | 同 类 | 同 包 | 子 类 | 不同的包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | — |
默认 | 没有访问控制修饰符 | √ | √ | — | — |
私有 | private | √ | √ | — | — |
方法覆盖与方法重载
方法重写(覆盖) (Override):子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
方法重载(Overload):对于类的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法式另一个方法的重载方法。
两者的几个不同之处:
- 方法覆盖要求签名必须一致,而方法重载要求参数签名必须不一致
- 方法覆盖要求返回类型必须一致,而方法重载对此不做限制
- 方法覆盖对方法的访问权限(不能缩小父类方法的访问权限)和抛出的异常 (子类不能抛出比父类更多(大)的异常)有特殊的要求,而方法重载没有要求。
构造方法
构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。
super和this关键字
super
和this
关键字都可以用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见。
方法或变量变被屏蔽的场合:
- 场合一:在一个方法内,当局部变量和类的成员变量同名,或局部变量和父类的成员变量同名,安装变量作用域规则,只有局部变量在方法内可见。
- 场合二: 方子类的某个方法覆盖了父类的一个方法,在子类的范围内,父类的方法不可见。
- 场合三:当子类中定义了和父类同名的成员变量时,在子类的范围内,父类的成员变量不可见。
当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;以此类推,任何Java对象,最先执行的总是 java.lang.Object
类的构造器。
子类的每个构造方法中均有默认的super()
,调用父类的空参构造。手动调用父类构造会覆盖默认的super()
。
super()
和 this()
都必须 是在构造方法的第一行,所以不能同时出现。
正如this
不能出现在static
修饰的方法中一样,super
也不能出现在static
修饰的方法中static
修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而 super
限定也就失去了意义。
即,只能在构造方法或实例方法内使用super关键字,而在静态方法和静态代码块内不能使用super关键字。
多态
动态绑定,向上、向下转型
TODO
Static
修饰符 | 类 | 成员方法 | 构造方法 | 成员变量 | 局部变量 |
---|---|---|---|---|---|
abstract | √ | √ | — | — | — |
static | — | √ | — | √ | — |
public | √ | √ | √ | √ | — |
protected | — | √ | √ | √ | — |
private | — | √ | √ | √ | — |
synchronized | — | √ | — | — | — |
final | √ | √ | — | √ | √ |
被static所修饰的成员变量和成员方法归某个类所有,它不依赖特定实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定位到它们。
static变量与C中的全局变量相似;Java语言不支持不属于任何类的全局变量,静态变量提供这一功能。
静态方法必须被实现,一个类的静态方法不能被定义为抽象方法。
方法的字节码都位于方法区:不管式实例方法,还是静态方法,它们的字节码都位于方法区。
static代码块:类中可以包含静态代码块,它不存在与任何方法体中。在Java虚拟机加载类时会执行这些静态代码块。
静态成员不能访问非静态成员。
Java编译器把Java方法的源程序代码编译成二进制的编码,称为字节码。
final
final
常量
final
修饰的类不能被继承final
修饰的方法不能被子类的方法覆盖final
修饰的变量表示常量,只能被赋值一次final
不用来修饰构造方法,因为父类的构造方法和子类的构造方法不存在覆盖关系private
修饰的方法不能被子类覆盖,因此默认也是final
的。- 对于
final
类型的实例变量,可以在定义变量时,或者在构造方法中进行初始化。
final 和 abstract 互斥
抽象类
abstract修饰符
abstract可用来修饰类和成员方法。
抽象方法,抽象方法没有方法体。
- 抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。如果子类没有实现父类中的抽象方法,那么子类也必须被定义为抽象类。
- 构造方法、静态方法不能是抽象方法。但抽象类中可以有非抽象的静态方法和非抽象的构造方法。
- 抽象方法和抽象类不能被
final
修饰符修饰。(final修饰的类不能有子类,final修饰的方法不允许被子类方法覆盖) - 抽象方法没有方法体。
public abstract class Base{ //抽象类
Base(){//构造方法不能是抽象方法
}
abstract void method1(); //抽象方法
void method2(){ //具体方法
System.out.println("具体方法");
}
}
接口 interface
接口对其成员变量和方法做了很多限制,接口的特征如下:
- 接口中的成员变量默认都是public、static、final 类型的,必须被显示初始化。(注意要理解这句话)
- 接口中的方法默认都是public、abstract类型的。
- 接口中只能包含上面两种类型的成员或方法。
- 接口没有构造方法(也不能有)
- 一个接口不能实现另一个接口,但它能继承多个其他接口。例如:A、B都是接口,
public interface C extends A,B{//}
- 接口必须通过类来实现它的抽象方法。
- 与子类继承抽象父类相似,当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类。
- 不允许创建接口的实例,但允许定义接口类型的引用变量,该变量引用实现了这个接口的类的实例。
- 一个类只能继承一个直接的父类,但能实现多个接口。
- 接口不能包含构造器和初始化块定义。
接口里可以包含:
- 成员变量(只能是静态常量;默认使用public staic final)
- 方法(只能时抽象方法、类方法(static)或默认方法(defalt);普通方法默认使用 public absract)
- 内部类(包括内部接口、枚举)的定义。(默认使用public static修饰)
接口中的所有成员都是public的(默认就是public)
interfce InterfaeA extends InterfaceB,InterfaceC{//接口可以相互继承
int PROP_A 5; //成员变量:默认 public static final
void testA(); //普通方法 默认 public astrat
// Java 8新增的default方法
// default方法是public的但不是static的,这就意味着只能使用接口的实现类的实例来调用这些默认方法。
default void print(String... msgs){
for(String msg:msgs){
System.out.println(msg);
}
}
//类方法:必须使用static修饰(如果不加static,IDEA会提示你抽象方法不可有方法体)
static String staticTest(){
return "接口里的类方法";
}
}
接口是一个完全抽象的类
抽象类与接口主要的两大区别:
- 在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,提高代码的可重用性;而接口中只能包含抽象方法。接口一旦被公布,就必须非常稳定,因为随意在接口中添加抽象方法,会影响到所有的实现类。
- 一个类可以实现(或继承)多个接口,但只能继承一个抽象类。
为什么Java不允许一个类有多个直接的父类?而可以实现多个接口? 见《Java面向对象编程》P223
Java 8新的接口特性
Java 8之前interface中的方法不能有方法体。从Java 8开始interface方法可以有方法体,但是需要使用关键字default
将其指定为默认方法。
default
方法是public
的但不是static
的,这就意味着只能使用接口的实现类的实例来调用这些默认方法。
补充:
Since Java 8, we can have static method in interface. 。
marker or tagged interface(标记或标记接口): 一个没有任何成员(方法)的接口。An interface that have no member is known as marker or tagged interface. For example: Serializable, Cloneable, Remote etc. They are used to provide some essential information to the JVM so that JVM may perform some useful operation.
内嵌的interface:interface中可以内嵌interface
内部类
inner Class:是定义在另一个类范围内的类。
内部类有如下特征:
- 一个内部类被编译成一个名为
OuterClassName$InnerClassName.class
类 - 内部类可以引用定义在它嵌套的外部类中的数据和方法
- 使用可见性修饰符定义内部类时,遵从和应用与在类成员上一样的可见性规则
- 可以将内部类定义为
static
。一个static内部类可以使用外部类的名字访问。一个static类是不能访问外部类的非静态成员的 - 内部类的对象经常在外部类中创建。也可从另一个类中创建一个内部类的对象。
- 如果该内部类是非静态的,就必须先创建一个外部类的实例,然后使用下面的语法创建一个内部类的对象:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
- 如果内部类是静态的,那么使用下面的语法为他创建一个对象:
OuterClass.InnerClass innerObject = new OuterClass.InnerClass();
- 如果该内部类是非静态的,就必须先创建一个外部类的实例,然后使用下面的语法创建一个内部类的对象:
外部类的上一级程序单元是包,所以它只有两个作用域:
- 同一个包内
- 任何位置
内部类的上一级程序单元是外部类,它具有4个作用域:
- 同一个类
- 同一个包
- 父子类
-
任何位置
- 非静态内部类不能拥有静态成员。
- 内部类比外部类可以多使用三个修饰符:private、protected、static
实例内部类
- 实例内部类中不能定义静态成员,而只能定义实例成员。
- 静态内部类(静态的实例内部类)中可以定义静态成员和实例成员(非静态)
由于实例内部类的实例自动持有其外部类的实例的引用,内部类实例存在时其外部类实例肯定已经存在,所以在内部类中,可以直接访问外部类的所以成员,包括成员变量和成员方法。
在多层嵌套中,内部类可以访问所有外部类的成员。
局部内部类
局部内部类是在一个方法中定义的内部类,它的可见范围是当前方法。局部内部类不能用访问控制修饰符和static修饰。
特点:
- 只能在当前方法中使用
- 不能包含静态成员(与实例内部类一样)
- 局部内部类不能用访问控制修饰符修饰
- 可以访问外部类的所有成员;此外,局部内部类还可以访问所在方法中的final类型的参数和变量。
静态内部类
匿名类
一种特殊的内部类,该类没有名字。
特点:
- 匿名类本身没有构造方法,但是会调用父类的构造方法。
A a = new A(v){ void method(){//...} }; a.method();
- 除了可以在外部类的方法内定义匿名类外,还可以在声明一个成员变量时定义匿名类。
- 匿名类除了可以继承类外,还可以实现接口。但最多只能继承或实现一个接口或类。
//比如在一个方法内 Thread t = new Thread(new Runnable()){ //匿名内部类,实现了Runnable接口 public void run(){ } }; t.start();
- 与局部内部类一样,如果匿名类位于一个方法中,还能访问所在方法的final类型的变量和参数
内部类的继承
泛型
泛型通配符
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接受数据,不能往该集合中存储数据。
举个例子大家理解使用即可:
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
tips:泛型不存在继承关系
Collection<Object> list = new ArrayList<String>();
这种是错误的。
受限泛型
通配符高级使用—-受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称
- 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称
- 意义:
只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
public class GenericClass {
public static void main(String[] args) {
/*
泛型不存在继承关系
*/
// Collection<Object> objectCollection = new ArrayList<String>();
//注意下面个泛型参数之间的关系
Collection<Object> objects = new ArrayList<>();
Collection<String> strings = new ArrayList<>();
Collection<Integer> integers = new ArrayList<>();
Collection<Number> numbers = new ArrayList<>();
//测试方法1
method01(objects);
method01(strings);
method01(integers);
method01(numbers);
//测试方法2
// method02(objects);
// method02(strings);
method02(integers);
method02(numbers);
//测试方法3
method03(objects);
// method03(strings);
// method03(integers);
method03(numbers);
}
//需求1: 定义一个方法,让其参数可以接收以上所有集合
// ? 表示匹配所有
public static void method01(Collection<?> col){}
//需求2: 定义一个方法,让其参数只可接收 numbers 和 integers
//提示: Integer的父类是Number。(设置Number为匹配的下线)
public static void method02(Collection<? extends Number> col){}
// 需求3:定义一个方法,让其参数只可以接收 objects 和 numbers
// 提示: Number的父类是Object。(设置Number为匹配的下限)
public static void method03(Collection<? super Number> col){}
}
IO流
IO流范围太宽,只介绍部分常用的类
StringWriter和StringReader
Java 8
Java8新增的Lambda表达式
Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。
只能有一个抽象方法,但是可以有
default
方法。
Lambda表达式的主要作用就是替代匿名内部类的烦琐语法。
Lambda表达式由三部分组成:
- 形参列表:
()
- 箭头:
->
- 代码块:
{}
Lambda表达式与函数式接口:
Lambda表达式的类型,也被称为“目标类型“(target type),Lambda表达式的目标类型必须是“函数式接口(functional interface)”。
函数式接口:代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。
Lambda表达式的结果被当成函数式接口的实例对象(对比:采用匿名类语法来创建的接口实例)所以可以使用 Lambda 表达式进行赋值。
Lambda表达式的两个限制:
- Lambda表达式的目标类型必须是明确的函数式接口
- Lambda表达式只能为函数式接口创建对象
Lambda表达式的使用场景:
- 将Lambda表达式赋值给函数式接口类型的变量
- 将Lambda表达式作为函数式接口类型的参数传给某个方法
- 使用函数式接口对Lambda表达式进行强制类型转换
/* 一个函数式接口 */
public interface Cook {
void makeFood();
//只可存在一个抽象方法
// void makeSoup();
}
public class DemoInvokeCook {
public static void main(String[] args) {
//使用方式一:
Cook cook = ()->{
System.out.println("Lambda表达式的使用场景:直接赋值");
};
cook.makeFood();
//使用方式二:
new DemoInvokeCook().invokeCook(()->{
System.out.println("Lambda表达式的使用场景: (方法参数) 饭做好了");
});
//使用方式三:
Object object = (Cook)()->{
System.out.println("Lambda表达式的使用场景:强制类型转换");
};
((Cook) object).makeFood();
}
private void invokeCook(Cook cook){
cook.makeFood();
}
}
使用Lambda来节省新能:
/**
* Lambda延迟运行:在某些情况下可以节省性能
* FunctionalInterface注解,jdk1.8中引入,用于表明某接口是函数式接口
*/
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
public class Demo01LambdaDelay {
public static void showLog(int level, MessageBuilder builder){
if (level == 1){//只有日志级别为1时才打印
String str = builder.buildMessage();
System.out.println(str);
}
}
public static void showLogOld(int level, String msg){
if (level == 1){
System.out.println(msg);
}
}
public static void main(String[] args) {
String msg1 = "你好,";
String msg2 = "小爱";
String msg3 = "同学。";
//当以参数形式传递这些字符串时,必定会先将其连接成单个字符串,不管level是否满足条件
showLogOld(1,msg1 + msg2 + msg3);
//注意:这里Lambda(局部匿名内部类)可以直接使用外部方法中的变量
// 我还一直想着要通过参数的形式传递给lambda
//TODO: 莫非这就是 lambda 节省性能的背后原理?
showLog(1,()-> msg1 + msg2 + msg3 );
//测试延迟执行
showLog(2,()-> {
//查看下面的打印
System.out.println("lambda执行");
return msg1 + msg2 + msg3;});
}
}
Lambda的省略规则:
- 形参列表:
- 可以省略形参类型
- 如果形参列表中只有一个参数,圆括号
()
可以省略
- 代码块:(要省全部省)
- 如果代码块中只包含一条语句,则允许省略代码块的花括号
{}
和该语句末尾的分号;
(必须同时) - 如果代码块中只有一条
return
语句,还可以省略retuen
关键字(必须同时按上一条规则进行省略)
- 如果代码块中只包含一条语句,则允许省略代码块的花括号
public interface Calculator {
int cal(int a, int b);
}
public interface Flying {
void fly(String str);
}
public class DemoLambda {
private static String outStr = "外部类中的静态字符串";
public static void main(String[] args) {
DemoLambda lambda = new DemoLambda();
//完整格式
lambda.calculatorMethod(1,2,(int a,int b)->{return a-b;});
//省略格式:省略int、return 、{} 和 ;
lambda.calculatorMethod(1, 2, (a, b) -> a - b);
//完整格式
lambda.flyMethod("打印fly", (String s)->{ System.out.println(s); });
//省略格式1:单一参数,省略()
lambda.flyMethod("打印fly", s ->{ System.out.println(s); });
//省略格式2:省略{} 和 ;
lambda.flyMethod("打印fly", s -> System.out.println(s));
//省略格式3:使用方法引用
lambda.flyMethod("打印fly", System.out::println);
//访问外部类中的outStr
lambda.flyMethod("fly", new Flying() {
@Override
public void fly(String str) {
System.out.println(str + outStr);
}
});
}
private void flyMethod(String str, Flying fly) {
fly.fly(str);
}
private void calculatorMethod(int a, int b, Calculator cal) {
int cal1 = cal.cal(a, b);
System.out.println("" + cal1);
}
}
方法引用和构造器引用:
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
绝大多数情况:lambda的参数会全部传递给现有操作方案(方法引用)。
- 引用类方法:
类名::类方法
- 引用特定对象的实例方法:
特定对象::实例方法
- 引用某类对象的实例方法:
类名::实例方法
- 引用构造器:
类名::new
如果能够使用方法引用或构造器引用IDEA会有提示,这里只做介绍,以后看到这种有两个::
应该知道它是一个Lambda表达式。
Java 9
Java平台,标准版 Oracle JDK 9中的新功能(上)
Java 平台,标准版 Oracle JDK 9 新功能(中)
Java平台,标准版 Oracle JDK 9中的新功能(下)
类的生命周期
《Java面向对象编程》第10章
对象的生命周期
《Java面向对象编程》第11章
反射
递归
递归( recursion) :
无退出条件(或递归调用太多)的递归会造成栈内存溢出(StackOverflowError)的原因:
调用方法会等待被调用方法的返回,如果一直递归则只有入栈而没有出栈,就会导致栈溢出。