Please enable Javascript to view the contents

Java基础知识

 ·  ☕ 26 分钟

此文还需完善,仅供个人使用

更多 Java 知识请参考:

一些基础概念

在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 JDK只发布二进制安装包,而OpenJDK只发布源码。也就是说任何人都可以基于OpenJDK源码构建二进制的OpenJDK发行版

从Java 11开始,是时转向Oracle JDK之外的世界了!_AdoptOpenJDK

OpenJDK和AdoptOpenJDK之间的区别

  • OpenJDK 有多种含义,可以称为:
  • AdoptOpenJDK - 由社区维护的预构建的OpenJDK二进制文件

各种 OpenJDK 之间的对比:OpenJDK Comparison Matrix

安装AdoptOpenJDK :

可在AdoptOpenJDK官网手动下载;这里在我的Windows中选择使用Scoop进行安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 先添加 java bucket
scoop bucket add java
# 搜索 jdk
# 也可以选择搜索 corretto 、Zulu
scoop search openjdk
'java' bucket:
    adoptopenjdk-hotspot (13.0.2-8)
    adoptopenjdk-lts-hotspot (11.0.6-10) #选择此版本
    adoptopenjdk-lts-openj9 (11.0.6-10-0.18.1)
    adoptopenjdk-openj9 (13.0.2-8-0.18.0)
    adoptopenjdk-upstream (11.0.3-7)
    # 省略部分结果
    openjdk (13.0.2-8)
    openjdk11 (11.0.2-9)
    openjdk13 (13.0.2-8)
    openjdk15 (15-8-ea)

这里选择: adoptopenjdk-lts-hotspot,其中 lts 表示长期支持版, hotspot 表示是 hotspot JVM。

安装Oracle JDK: (不建议)

  1. Oracle下载安装包
  2. 运行安装程序
  3. 选择要安装的组件
  • 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_HOMEJAVA_HOME:D:\Program Files\java\jdk1.8

CLASSPATHCLASSPATH:%java_home%\lib

CLASSPATH环境变量的作用:显式指定Java类(.class文件)的搜索路径。1.4版本之前的JDK才需要设置,现在JRE默认已经能够自动搜寻当前路径.和JDK下的lib文件夹;如果你显式设置了CLASSPATH,则会跳过默认值,所以记得将.包含在里面。不推荐设置 CLASSPATH

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讲义》

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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()的逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    @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);
    }
1
2
//优势是?
Objects.equals();

垃圾回收机制

时间和日期

《Java核心技术卷 I》第4章 4.1.1中有关于这些介绍。

Date表示时间,Calendar表示日期?

表示时间点的Date,表示大家熟悉的日历表示法的GregorianCalendar类。其中GregorianCalendar类扩展了一个更加通用的Calendar类。

Date类只提供了少量的方法用来比较两个时间点。例如before和after方法。(其他方法不推荐使用)

两者之间的转换:

GregorianCalendar -> Date

1
Date time = calendar.getTime();

Date -> GregorianCalendar

1
calendar.setTime(new Date());

获取当前日期和时间:

1
2
3
GregorianCalendar d = nwe GregorianCalendar();
//更易使用的方法
GregorianCalendar.getInstance();

常用工具类和方法

检测是否为 null

1
2
// 优势是什么?
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导入后,使用导入某类的公共静态成员时可以省略类名。
(静态方法和静态域属于某个类;而类属于某个包)

数组

声明、定义和初始化示例:

1
2
3
4
5
//声明
int[] arrayName;//推荐
int arrayName[];//淘汰


**初始化:**数组元素中的基本数据类型值会自动初始化成空值(对于数字和字符,就是0,对于布尔型,就是false)

数组大小不可变。

数组是一个引用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        int[] a1 = {1,2,3,4,5};
        int[] a2;
        a2=a1;
        for (int i = 0; i < a2.length; i++) {
            a2[i] = a2[i]+1;
        }

        for (int i : a1) {
            //错误的语句
            //System.out.println(a1[i]);
            System.out.println(i);
        }

多维数组

Arrays工具类

Arrays类里包含的一下static修饰的方法可以直接操作数组。

  • 二分查找
  • 复制数组
  • 比较两个数组是否相等(长度和元素都相等)
  • 对数组排序
  • 转换为字符串

另外:Java 8还新增了提高并行能力的对应方法。

数组与集合的比较

给出一个数组和集合:

1
2
3
4
5
6
//定义并初始化一个数组
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是有序的,但是没有索引。

打印数组和集合:

1
2
3
4
5
6
7
8
9
//直接打印数组变量(该变量中保存的地址)
System.out.println(arr); // 结果类似: [I@4554617c

//直接打印集合对象
//因为集合的实现类都重写了toString()方法
System.out.println(list); //[11]

//打印数组的方式
System.out.println(Arrays.toString(arr));

获取数组和集合中的元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    // 定义数组,存储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:由哈希表保证元素的唯一性(“不保证有序"和"保证无序"不等价,HashSet 的 iterator 是前者而不是后者,所以在一次运行中看到有序的结果也是正常的,但不能依赖这个有序行为。)
      • LinkedHashSet:存取有序,元素唯一, 由链表保证存取有序,有哈希表保证元素唯一
      • TreeSet:由树状结构保证元素的唯一性

双列集合

没有索引,没有提供迭代器。

  • Map:
    • HashMap:
      • LinkedHashMap:
    • TreeMap:

如果新添加的键值对的键重复,就会覆盖之前该键的值。

HashSet的底层依赖于HashMap实现(顺序不要错了)。

  1. Java只支持单继承,不支持多继承。
  2. Java支持多层继承(继承体系)。

访问权限

访问控制的4个级别:

  • public:公开级别
  • protected:受保护级别,向子类同一个包中的类公开
  • 默认级别:没有访问控制修饰符,向同一个包中的类公开
  • private:私有级别,只有类本身可以访问,不对外公开
访问级别 访问控制修饰符 同 类 同 包 子 类 不同的包
公开 public
受保护 protected
默认 没有访问控制修饰符
私有 private

方法覆盖与方法重载

方法重写(覆盖) (Override):子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

方法重载(Overload):对于类的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法式另一个方法的重载方法。

两者的几个不同之处:

  • 方法覆盖要求签名必须一致,而方法重载要求参数签名必须不一致
  • 方法覆盖要求返回类型必须一致,而方法重载对此不做限制
  • 方法覆盖对方法的访问权限(不能缩小父类方法的访问权限)和抛出的异常 (子类不能抛出比父类更多(大)的异常)有特殊的要求,而方法重载没有要求。

构造方法

构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。

super和this关键字

superthis 关键字都可以用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见。

方法或变量变被屏蔽的场合:

  • 场合一:在一个方法内,当局部变量和类的成员变量同名,或局部变量和父类的成员变量同名,安装变量作用域规则,只有局部变量在方法内可见。
  • 场合二: 方子类的某个方法覆盖了父类的一个方法,在子类的范围内,父类的方法不可见。
  • 场合三:当子类中定义了和父类同名的成员变量时,在子类的范围内,父类的成员变量不可见。

当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;以此类推,任何Java对象,最先执行的总是 java.lang.Object 类的构造器。

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()

super()this() 都必须 是在构造方法的第一行,所以不能同时出现。

正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而 super限定也就失去了意义。

即,只能在构造方法或实例方法内使用super关键字,而在静态方法和静态代码块内不能使用super关键字。

多态

动态绑定,向上、向下转型

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修饰的方法不允许被子类方法覆盖)
  • 抽象方法没有方法体。
1
2
3
4
5
6
7
8
9
public abstract class Base{ //抽象类
    Base(){//构造方法不能是抽象方法
        
    }
    abstract void method1(); //抽象方法
    void method2(){ //具体方法
        System.out.println("具体方法");
    }
}

接口 interface

接口对其成员变量和方法做了很多限制,接口的特征如下:

  1. 接口中的成员变量默认都是public、static、final 类型的,必须被显示初始化。(注意要理解这句话)
  2. 接口中的方法默认都是public、abstract类型的。
  3. 接口中只能包含上面两种类型的成员方法
  4. 接口没有构造方法(也不能有)
  5. 一个接口不能实现另一个接口,但它能继承多个其他接口。例如:A、B都是接口,public interface C extends A,B{//}
  6. 接口必须通过来实现它的抽象方法。
  7. 与子类继承抽象父类相似,当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类。
  8. 不允许创建接口的实例,但允许定义接口类型的引用变量,该变量引用实现了这个接口的类的实例。
  9. 一个类只能继承一个直接的父类,但能实现多个接口。

  • 接口不能包含构造器和初始化块定义。

接口里可以包含:

  • 成员变量(只能是静态常量;默认使用public staic final)
  • 方法(只能时抽象方法、类方法(static)或默认方法(defalt);普通方法默认使用 public absract)
  • 内部类(包括内部接口、枚举)的定义。(默认使用public static修饰)

接口中的所有成员都是public的(默认就是public)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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类型的变量和参数

内部类的继承

泛型

泛型通配符

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

此时只能接受数据,不能往该集合中存储数据。

举个例子大家理解使用即可:

1
2
3
4
5
6
7
8
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的父类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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){}
 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
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表达式进行强制类型转换
1
2
3
4
5
6
/* 一个函数式接口 */
public interface Cook {
    void makeFood();
    //只可存在一个抽象方法
//    void makeSoup();
}
 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
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来节省新能:

1
2
3
4
5
6
7
8
/**
 * Lambda延迟运行:在某些情况下可以节省性能
 * FunctionalInterface注解,jdk1.8中引入,用于表明某接口是函数式接口
 */
@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
 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
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关键字(必须同时按上一条规则进行省略)
1
2
3
public interface Calculator {
    int cal(int a, int b);
}
1
2
3
public interface Flying {
    void fly(String str);
}
 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
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 9新特性详解 - CSDN博客

类的生命周期

《Java面向对象编程》第10章

对象的生命周期

《Java面向对象编程》第11章

反射

递归

递归( recursion) :

无退出条件(或递归调用太多)的递归会造成栈内存溢出(StackOverflowError)的原因:

调用方法会等待被调用方法的返回,如果一直递归则只有入栈而没有出栈,就会导致栈溢出。

您的鼓励是我最大的动力
alipay QR Code

Felix
作者
Felix
如无必要,勿增实体。

3

目录