java复习(华南农业大学)

Java复习

第一讲

JVM

JVM,java虚拟机,是字节码解释器,用于解释字节码文件。

Java程序的编码特点

编译器先把.java文件编译成.class文件(字节码文件),然后再通过解释器来解释执行字节码文件

字节码最大的好处是可跨平台执行

Java程序的分类

  1. Application,称为Java应用程序

    可以在Java平台上独立运行的一种程序

    主类并不一定要求是public类

  2. Applet,称为Java小程序

    内嵌在HTML文件里,需要在浏览器的支持下才能执行

    主类继承自JApplet,必须为public类

第二讲

基本数据类型

Java中共有8种基本数据类型

数据类型关键字占用字节数默认数值取值范围
布尔型boolean1falsetrue,false
字节型byte10-128~127
短整型short20-32768~32767
整型int40-2147483648~2147483647
长整型long80-9223372036854775808~9223372036854775807
单精度浮点型float40.0F负数范围:-3.4028235E+38~-1.4E-45
正数范围:1.4E-45~3.4028235E+38
双精度浮点型double80.0D
字符型char2“\u0000”“\u0000”~“\uffff”

标识符

任何一个变量、常量、方法对象和类的名字就是标识符

标识符命名规则

  • 标识符可以由字母、数字和下划线、美元符号等组合而成;
  • 标识符必须以字母、下划线或美元符号开头,不能以数字开头
  • 标识符的命名不能与关键字冲突

常量

常量的声明

如:

final int MAX = 10;

final double PI = 3.14f;

常量的标识符全用大写字母表示,若有多个单词,则以下划线分割;

数据类型转换

优先级

由低到高

byte short char int long float double

自动类型转换

级别低的类型向级别高的转换时。例如:

float x = 100;

int k = ‘A’;

强制类型转换

级别高的类型向级别低的转换时。例如:

int x = (int)100.0;

byte k = (byte)200;

运算转换规则

不同类型数据算术运算时的转换规则:

1.如果运算对象之一是double,就将另一个转换double型。

2.否则,如果运算对象之一是float,就将另一个转换为float型。

3.否则,如果运算对象之一是long,就将另一个转换为long型。

4.否则,两个运算对象都转换为int型。

关键字

Java共有50个关键字

abstractcontinuefornewswitch
assertdefaultgotopackagesynchronized
booleandoifprivatethis
breakdoubleimplementsprotectedthrow
byteelseimportpublicthrows
caseenuminstanceofreturntransient
catchextendsintshorttry
charfinalinterfacestaticvoid
classfinallylongstrictfpvolatile
constfloatnativesuperwhile

if语句

if语句的嵌套规则:
else总是与同一块中最近的未匹配的if子句匹配.

switch语句

switch (switch-expression) {
  case value1: statement(s)1;
               break;
  case value2: statement(s)2;
               break;
  .....
  case valueN: statement(s)N;
               break;
  default: statement(s) for default;
}

说明:

  • switch-expression的计算结果必须是charbyteshortint

  • value1, …,valueN的类型必须与switch-expression的类型相同

循环语句

// 第一种	while语句
while (循环条件) {
	语句();
}
// 第二种 	do-while语句
do {
	语句();
} while (循环条件)
// 第三种 for语句
for (初始化操作; 循环条件; 每次循环后的操作) {
    语句();
}
// for循环标号
outer: for (int i = 0; i < 10; i++) {
	inner: for (int j = 0; j < 10; j++) {
        if (i * j < 50) {
            // break outer;  跳到最外层的for循环
            // continue outer;  结束outer的for循环的当前迭代
        }
    }
}

break;
跳出包含它的最内层循环, 或跳出包含它的最内层switch结构。

break 标号;
跳出标号指明的循环。

continue;
结束包含它的最内层循环的当前迭代,程序控制转到循环体末尾。

continue 标号;
结束标号指明的循环的当前迭代。

第三讲

数组的基本概念

  • Java中的数组具有基本的数组特征;
  • Java数组是一种线性数据结构, 它是一个有序集, 存储数据类型相同、个数固定的多个元素;
  • Java数组是对象方式提供的,与C语言不同

Java数组的使用通常有3个步骤:

  1. 声明数组(声明数组变量)

  2. 分配数组的内存空间(创建数组对象)

  3. 使用数组元素

基本特点

数组分配内存空间时确定数组的大小, 不能改变。

使用数组时, 数组名.length 表示数组的大小。

数组创建后, 其元素有默认值

数值型默认为: 0

char 型默认为: ‘\u0000’

boolean 型默认为: false

一维数组

第一步:声明数组变量

语法:

(1)数据类型[ ] 数组变量;

(2)数据类型 数组变量[ ]; 不推荐使用

示例:

int[] arr;
int arr2[];	

第二步:创建数组对象

创建数组对象,即为数组分配内存空间。语法如下:

数组变量 = new 数组元素类型[数组大小];

第三步:使用数组元素

通过数组变量和下标访问数组元素:数组名[下标]

说明:

  • Java的数组下标是基于0的.

  • Java的数组下标必须是整数或整数表达式.

多维数组

以二维数组为例

3种声明二维整型数组的方式:

int[][] matrix;
int matrix[][]; //不推荐使用
int[] matrix[]; //不推荐使用

创建二维数组对象:

// 方式1:同时创建整个多维数组对象
int[][] x = new int[3][4]; //同时指定行、列数

// 方式2:分别创建 “行对象”和 “列对象”
int[][] matrix = new int[5][];           //创建 “行对象”
matrix[0] = new int[] { 1, 2, 3, 4, 5 }; //创建 “列对象”
matrix[1] = new int[] { 2, 3, 4, 5 };
matrix[2] = new int[] { 3, 4, 5 };
matrix[3] = new int[] { 4, 5 };
matrix[4] = new int[] { 5 };

数组基本操作

两种遍历循环
// 常规循环
for (int i = 0; i < arr.length; i++) {
    // 函数体
}
// foreach 循环
for (元素类型 元素变量 : 数组变量) {
    // 函数体
}
复制数组
int[] a = {1, 2, 3, 4, 5};
// 用循环语句复制数组
int[] b = new int[a.length];
for(int i=0; i<a.length; i++) {
    b[i] = a[i];
}
// 使用System类的arraycopy方法
int[] b = new int[a.length];
System.arraycopy(a, 0, b, 0, a.length);
// 使用数组对象的clone方法复制数组
int[] b = a.clone();

字符串

字符串的基本概念
  1. String类用来描述和处理字符串类型

  2. 所有的字符串直接量, 例如"abc", 都是String

  3. String类的对象是不可变对象, 即它们的值在对象创建后就不能改变了

注:第三点中的不可变对象指的是存在于堆内存中的字符串对象,而不是字符串变量的引用变量(存在于栈内存)

构造方法
  1. 使用字符串直接量构造

String message = new String(“welcome to Java”);

  1. 字符串的快捷初始化方法

String message = “welcome to Java”;

  1. 使用字符数组构造

char[] charArray = [‘G’, ‘o’, ‘o’, ‘d’, ‘ ’, ‘D’, ‘a’, ‘y’];

String message = new String(charArray);


相同的字符串常量在java中的地址是一样的

String s = "welcome to Java"; // 从字符串常量池中取出字符串常量的地址
String s1 = new String("welcome to Java");  // new关键字创建了一个新对象,并分配了空间
String s2 = "welcome to Java"; // 从字符串常量池中取出字符串常量的地址

System.out.println(s == s1); // false
System.out.println(s == s2); // true

System.out.println(s.equals(s1)); // true
System.out.println(s.equals(s2)); // true
字符串常用方法
intern( )

当前的字符对象(通过new出来的对象)可以使用intern方法从常量池中获取,如果常量池中不存在该字符串,那么就新建一个这样的字符串放到常量池中。

String.format( )

对整数进行格式化:%[index$][标识][最小宽度]转换方式

标识

‘-’ 在最小宽度内左对齐,不可以与“用0填充”同时使用
‘#’ 只适用于8进制和16进制,8进制时在结果前面增加一个0,16进制时在结果前面增加0x
‘+’ 结果总是包括一个符号(一般情况下只适用于10进制,若对象为BigInteger才可以用于8进制和16进制)
’ ’ 正值前加空格,负值前加负号(一般情况下只适用于10进制,若对象为BigInteger才可以用于8进制和16进制)
‘0’ 结果将用零来填充
‘,’ 只适用于10进制,每3位数字之间用“,”分隔
‘(’ 若参数是负数,则结果中不添加负号而是用圆括号把数字括起来(同‘+’具有同样的限制)

转换方式

d-十进制 o-八进制 x或X-十六进制

第四讲

类的定义

[ 修饰符 ] class 类名 {

}

类的命名

  • 类名使用名词性英文单词或词组;

  • 使用小写字母,每个单词的首字母大写

类的成员变量

[ 修饰符 ] 数据类型 成员变量名 [ = 初始值 ] ;

  1. Java的成员变量称为数据域,用于定义类的属性
  2. 数据类型可以是Java任何一种基本数据类型或类、数组等引用类型
  3. 数据域变量命名通常使用名词性单词或词组,当由多个单词组成时,从第2个单词开始首字母大写

+++

  • 在方法中命名的局部变量可以与成员变量同名
  • 在方法中,局部变量成员变量命名冲突时,局部变量优先
  • 局部变量在作用域范围不重叠的情况下可以命名重复;
局部变量与成员变量
  • 修饰符
    • 局部变量只能使用final修饰符
    • 成员变量可以根据需要使用多种修饰符
  • 存储方法
    • 局部变量存储在
    • 成员变量属于对象,存储于
  • 生命周期
    • 局部变量随方法调用的开始产生,方法调用结束结束后消失
    • 成员变量随对象的创建而存在
  • 初始化
    • 局部变量没有自动初始值,必须显式初始化或赋值后使用
    • 成员变量有默认初始值

类的方法

[ 修饰符 ] 返回类型 方法名 ( 形式参数列表 ) {

​ //方法体

}

  • Java中没有独立的方法,方法必须定义在类的内部
  • 方法用于定义对类的某些数据域的操作,实现类的内部功能,是类与外部交互的窗口
  • 方法命名通常使用动词或动词性词组, 当由多个单词组成时, 从第2个单词开始首字母大写

构造方法

构造方法是一种特殊方法, 创建对象时调用, 用于初始化对象的数据域变量。

语法要求

  • 构造方法必须与所在的类有相同的名字
  • 构造方法不能声明返回类型, 连void也没有
  • 构造方法可见性修饰符无特定要求

特征

  • 当类中没有明确声明构造方法时, 隐含声明一个方法体为空的无参构造方法,称为 默认构造方法
  • 一个类可以有多个构造方法, 通过方法重载实现, 即多个构造方法的形式参数的个数或类型不同

方法中的参数传递

  • Java语言中, 所有参数都是值传递, 即发生方法调用时, 方法调用语句中的实际参数把其值单向传递给形式参数

  • 方法中修改形式参数的值, 对 “实际参数” 的值没有影响。

  1. 基本类型的变量的值是数据,所以基本类型的形参不会引起实参变化
  2. 引用类型的变量的值是地址,实参会将地址传递给形参,所以形参变化造成实参引用变化

Java 5引入了可变参数,允许接受个数不定的参数

可变参数传参时的形式:

​ 数据类型… 可变参数名

  • 可变参数必须位于形参的最后一项
  • 可变参数在方法中以数组形式调用

多态

  • 基于重载实现的静态多态多个操作具有相同的名称,但是接受不同的消息类型

  • 基于继承和覆盖实现的动态多态:同一操作被不同类型的对象调用产生不同的行为

  1. 多态在类的继承层次结构中实现
  2. 表现多态的形式是子类覆盖父类的实例方法
  3. 通过父类的引用去访问继承层次中不同类的对象,调用覆盖的方法时产生多态(通过下面的例子解释)
public class Person {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Student(); // Student是Person的子类
        Person p3 = new CollegeStudent(); // CollegeStudent是Student的子类
        p1.sayHi(); 
        p2.sayHi();
        p3.sayHi();
    }
    public void sayHi() {
        System.out.println("hi,person");
    }
}
// 输出结果
/*
 hi,person
 hi,Student
 hi,CollegeStudent
*/

注:如果子类重写父类的静态方法,子类对象的向上转型对象不能调用子类静态方法, 只能调用父类静态方法

即:多态只针对实例方法

匿名对象

  1. 对象创建后,如果没有将对象的地址存放到引用变量中,则该对象称为匿名对象

  2. 如果创建一个对象只在代码中使用一次,可以使用匿名对象

  3. 没有变量引用,代码执行完后会被当成垃圾对象回收

修饰符

类修饰符
修饰符含义
public将一个类声明为公共类,它可以被任何对象访问
abstract将一个类声明为抽象类,没有实现方法,需要子类提供方法的实现,所以不能创建该类的实例
final将一个类声明为最终类即非继承类,表示它不能被其他类所继承
缺省缺省修饰符时,则表示只有在相同的包中的对象才能使用这样的类
成员变量修饰符
修饰符含义
public公共访问控制符。指定该变量为公共的,它可以被任何对象的方法访问
private私有访问控制符。指定该变量只允许自己类的方法访问,其他任何类(包括子类)中的方法均不能访问此变量
protected保护访问控制符。指定该变量只可以被它自己的类及其子类或同一个包中的其他类访问,在子类中可以覆盖此变量
缺省缺省访问控制符时,则表示在同一个包中的类可以访问此成员变量,而其他包中的类不能访问该成员变量
final最终修饰符。指定此变量的值不能改变
static静态修饰符。指定该变量被所有对象共享,即所有的实例都可使用该变量
成员方法
修饰符含义
public公共访问控制符。指定该方法为公共的,它可以被任何对象的方法访问
private私有访问控制符。指定该方法只允许自己类的方法访问,其他任何类(包括子类)中的方法均不能访问此方法
protected保护访问控制符。指定该方法只可以被它自己的类及其子类或同一个包中的其他类访问,在子类中可以覆盖此方法
缺省缺省访问控制符时,则表示在同一个包中的类可以访问此方法,而其他包中的类不能访问该成员方法
final最终修饰符。指定此方法不能被覆盖
static静态修饰符。指定不需要实例化一个对象就可以调用的方法
abstract抽象修饰符。指定该方法只声明方法头,而没有方法体,抽象方法需在子类中被实现
native本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的

第五讲

Java使用包(package) 对类进行组织和管理.

使用包的理由如下:

  1. 查找、定位类

  2. 避免命名冲突

  3. 便于发布软件

  4. 保护类

包的命名习惯
  1. 保证包名的唯一性非常重要, Java建议采用Internet域名的倒序作为包的前缀
  2. 通常包名都用小写字母命名
  3. Java中,包名与文件系统的目录结构一一对应
在包中添加类
  • Java中的每个类都属于某一个包, 类在编译时被添加到包中.

  • 默认情况下, 类在编译时放在当前目录(默认包).

把类添加到指定包中的方法:

在类的源代码最前端加上:

package packageName(包名);

import语句

使用包中类的方法
  • 使用类的全称(直接使用)

    javax.swing.JOptionPane var; // 变量定义

  • 使用import语句导入包中的类

    • 导入包中全部类

      import javax.swing.*;

    • 导入包中指定类

      import javax.swing.JOptionPane;

注意:

import语句位于package语句之后,类定义之前。

this关键字

this关键字主要有2种使用方式:

  1. 在类的构造方法中使用,调用本类的其他构造方法。

    this(实参表)

    注意:如果使用该语句,必须在构造方法的第一句

  2. 在类的构造方法实例方法中使用,代表对当前对象的引用

    访问实例变量语法:this.实例变量名

    调用实例方法语法:this.实例方法名(实参表)

注:每个构造方法和实例方法中都隐含有一个this引用

静态成员和实例成员

  • static修饰是静态成员
  1. 静态成员变量
  2. 静态方法
  • 未用static修饰是实例成员
  1. 实例成员变量

  2. 实例方法

实例成员:属于对象

静态成员:属于类

实例变量和静态变量
  • 不同对象的实例变量互不相同

    • 实例变量只有其对象创建后才会分配内存空间
    • 使用new运算符创建对象时,每个对象的实例变量占用各自不同的内存空间
  • 所有对象共享静态变量

    • 静态变量在类加载到内存时分配内存空间。
    • 使用new运算符创建对象时,各个对象的实例变量共享已经存在的静态变量的内存空间。
  • 通过类名直接访问静态变量

    • 静态变量的使用方式是:类名.静态变量
    • 实例变量的使用方式是:引用变量.实例变量

创建对象后,也可以使用引用变量.静态成员方式访问静态数据域静态方法 (但是不建议这样写)

初始化器

初始化器是直接在类中定义的用一对 {} 括起来的语句组。

  • 静态初始化器使用static关键字修饰,用来初始化静态变量。

  • 实例初始化器没有修饰关键字,用来初始化实例变量。

注:一个类可以有0个或多个静态和实例初始化器。

静态初始化器的执行:类首次加载到内存时,首先是静态数据域变量的变量初始化;然后按排列顺序执行类中static初始化器。

实例初始化器的执行:每次使用 new 构造方法(); 创建对象时,首先是实例数据域变量的变量初始化,然后开始执行本类的构造方法;

在构造方法第一条语句执行之前,按排列顺序执行类中的实例初始化器,然后执行构造方法中的剩余语句。

方法重载

一个类中2个或更多的方法具有相同的名称但不同的形参列表, 称为方法重载(method overloading)

被重载的方法的形参必须不同,返回类型可以不同

  • 形参个数不同

  • 形参的类型不同

  • 形参的排列顺序不同

当进行方法调用时, Java编译器寻找最合适的方法匹配调用

第六讲

类的继承

在面向对象程序设计中,可以从已有的类派生出新类,称为继承(inheritance)

  • Java程序中的每一个类均显式或隐式的派生自一个已存在的类
  • 没有指明来源的类隐含由java.lang.Object派生,它是Java程序中所有类的公共起源,它自身没有继承其它类
  • Java 只支持单继承,一个子类只能继承一个父类

语法:

修饰符 class 子类名 extends 父类名{}

继承的特点

  • 子类继承父类的成员变量方法是指子类可以将它们作为自己定义的成员, 访问权限保持不变。
  • 子类继承父类有2个方面的含义
    • 继承父类可访问的数据域方法
      • 子类与父类在同一包中,继承:public, protected, 缺省
      • 子类与父类在不同包中, 继承:public,protected
    • 扩展添加新的数据域新方法

说明:子类不继承父类的构造方法不可访问的成员(如private成员),但是可以通过特定方式间接使用他们。


子类使用父类中的成员:

  • 使用从父类中继承过来的数据域(例如a)时,需要用getA或者this.getA( )或者super.getA( )
  • 使用从父类中继承过来的方法(例如test( ))时,需要用test( )或者this.test( )或者super.test( )

子类的构造方法

子类不继承父类的构造方法,子类构造方法中必须调用父类的构造方法,调用方式为:

super( ); //调用父类的无参构造方法

super(参数); //调用父类中与参数匹配的构造方法

  1. 调用父类构造方法的语句必须出现在子类构造方法中的第一句
  2. 子类构造方法没有显式调用父类构造方法,则编译器把super()作为子构造方法的第一条语句

子类构造方法也可以调用本类中重载的其它构造方法(this):

this(参数)

但是构造方法只能调用一个,super() this()只能调用其中一个,且二者出现时都必须在第一行


构造一个类的实例时, 会沿着继承链依次调用每个父类的构造方法, 称为构建方法调用链
请添加图片描述

构建方法调用链执行的顺序与调用顺序相反

public class Test3 {
    public static void main(String[] args) {
        Faculty faculty = new Faculty();
    }
}
class Person {
    public Person() { System.out.println("Person()"); }
}
class Employee extends Person {
    public Employee() {
        this("call Employee(String)");
        System.out.println("Employee()");
    }
    public Employee(String s) { System.out.println(s); }
}
class Faculty extends Employee {
    public Faculty() { System.out.println("Faculty()"); }
}
//返回结果:
/*
 Person()
 call Employee(String)
 Employee()
 Faculty()
 */

方法覆盖

子类继承父类的实例方法,可以根据以下规则重写(覆盖)该方法:

  • 子类定义一个与继承的实例方法同名的方法
  • 子类方法的返回类型要求:
    • 当父类方法返回值是基本类型void时:与父类方法的返回类型相同
    • 当父类方法返回值是引用类型时:与父类方法返回类型相同或是父类返回类型的子类
  • 子类方法的形参个数形参类型必须与父类方法完全相同。
  • 参数名称可以不同
  • 子类方法的访问权限必须大于等于父类方法的访问权限

子类也可以对父类的静态方法进行重写,但这种语法不是覆盖,如果加上**@Override**,会有语法错误

final关键字

  • final不能被继承
  • final方法不能被子类重写,可以继承
  • final变量的值不能发生改变
  • final修饰的成员成员变量可以被继承

子类访问父类实例成员

  • 没有Override的情况下

    子类可以通过this.变量名super.变量名 两种形式访问继承自父类的实例成员

  • 子类Override了父类的实例成员的情况下

    • this. 表示调用子类重写的方法
    • super. 表示调用继承自父类的方法

hashCode方法覆盖

子类在重写equals方法时,也应该重写hashCode方法

// 例子
@Override
public int hashCode() {
	return Objects.hash(x, y, radius); // 传入的参数与equals方法中比较的数据一致
}
// 重写的是Object类中的hashCode方法,调用的是Objects类中的hash方法

调用java.util.Objects类中的hash方法,传入的参数与重写的equals方法用来判断的数据成员的一样

对象类型转换

  1. 父类类型的对象引用可以引用一个父类对象或者任何的子类对象,称为隐式类型转换(向上类型转换)

  2. 如果没有显示地强制类型转换,一个子类类型的对象引用不可以引用父类对象。把父类类型强制转换为子类类型称为向下强制类型转换

  3. 如果两个类是平等的,即任何一个都不是另一个的祖先或者后代,则这两个类不能进行类型转换

instanceof运算符

instanceof运算符用于判断一个对象是否是一个类或其子类的实例

语法:对象 instanceof 类;


如果想精确判断一个对象是什么类,可以用:对象.getClass() == 类名.class

抽象类

抽象方法:只有方法头,没有方法体,用abstract修饰

抽象类:用abstract修饰的类,不能用new创建对象


关于抽象类和抽象方法:

  • 包含抽象方法的类必须是抽象的,但也允许声明没有抽象方法的抽象类
  • 抽象类不能用new关键字实例化,但包含构造方法
  • 具体的父类也可以派生抽象的子类
  • 子类可以将父类的具体方法覆盖为抽象方法(少用)
  • 抽象类不能创建对象,但可以声明对象引用变量

接口

接口是一种与类相似的结构

接口结构包括:

  • 静态常量(可继承)
  • 抽象方法(可继承)
  • 静态方法(不可继承)
  • 默认方法(可继承)

接口定义的语法:

[public] interface 接口名称 [extends 父接口1, ..., 父接口n] {}
  1. interface前的public省略时,接口的访问范围为package
  2. interface的extends部分省略时,没有默认的父接口
  3. 接口成员前面的public无论是否省略,均为public
  4. 接口方法的abstract省略后该方法依然是abstract方法
  5. 接口源文件命名与类相同,接口名.java
  6. 接口不能用new创建实例对象
  7. 接口中的默认方法静态方法属于具体实现方法,需要加方法体
  8. 默认方法default修饰;默认方法也属于实例方法
实现和引用

接口可以implements多个,但父类只能有一个

class 子类 extends 父类 implements 接口1,...,接口n{}

implements有继承的含义,即子类会继承其实现的所有接口中可以继承的成员。

子类继承接口时:

  • 可以继承接口中所有的静态常量数据成员
  • 可以继承接口中所有抽象方法默认方法
  • 不继承接口中静态方法
  • 子类如果没有Override其实现接口的全部抽象方法,则子类需要使用abstract修饰

  • 接口可以声明变量,称为接口变量
  • 接口变量可以存放一个对象的引用(地址),但要求该对象的类实现对应接口
  • 接口回调
    • 指把实现了某接口的类的对象引用赋值给该接口声明的接口变量
    • 通过接口变量去调用被类实现的接口的方法
    • 通过接口变量无法调用类中非实现接口中声明的方法

请添加图片描述

请添加图片描述

请添加图片描述

Comparable接口

java.lang.Comparable实现默认比较

// Comparable接口源码
package java.lang;
public interface Comparable<T> {
	public int compareTo(T o);
}
/*
 * e1.compareTo(e2)
 * 返回值==0表示:e1 == e2
 * 返回值>0表示: e1 > e2
 * 返回值<0表示: e1 < e2
 */

Comparator接口

java.util.Comparator实现灵活比较

// 源码
package java.util;
public interface Comparator<T> {
	int compare(T o1, T o2);
}
/*
 * c.compare(e1,e2), c是比较器对象
 * 返回值==0表示:e1 == e2
 * 返回值>0表示: e1 > e2
 * 返回值<0表示: e1 < e2
 */

Cloneable接口

Cloneable接口是标记性接口,接口中没有任何方法声明,不需要重写任何方法

// 源码
package java.lang;
public interface Cloneable {}  

实现类的clone操作,需要完成2项工作:

  1. 类必须实现java.lang.Cloneable接口
  2. 类需要覆盖Object类的clone方法
浅克隆

在浅克隆中,被克隆对象中的引用类型的变量并不会被克隆(即克隆出来的对象与被克隆对象中的引用类型的变量指向的地址是相同的)

// 浅克隆实现
public class House implements Cloneable{
  private int id;
  private double area;
  private Date whenBuilt;
  @Override
  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      return null;
    }
}
    
// 使用克隆
House h1 = new House(1, 137.00);
House h2 = (House) h1.clone(); // clone()返回的是Object类型,需要强制类型转换
深克隆

解决了浅克隆不能克隆引用变量的问题

// 深克隆实现
@Override
public Object clone() {
  try {
    House t = (House) super.clone();
    t.whenBuilt = (Date) this.whenBuilt.clone(); // 强制类型转换;引用类型变量额外克隆
    return t;  
  } catch (CloneNotSupportedException e) {
    return null;
  }
}

一个类如果实现了接口,通常需要重写equals方法和hashCode方法

第七讲

错误的种类

  • 语法错误
    • 程序中违反了语言的规则
    • 可以由编译器发现
  • 逻辑错误
    • 程序没有按照预期方案执行
    • 通过测试和调试程序来发现和改正
  • 运行错误(语义错误)
    • 程序运行过程中,运行环境发现了不可执行的操作
    • Java通过异常处理来解决运行错误

异常的种类

Java异常是java.lang.Throwable的后代类的对象实例

  • 系统错误(System Error)
    • 由JVM抛出并在Error类中描述
    • 很少发生,如果发生只能通知用户并尽量稳妥地结束程序
  • 运行异常(Runtime Exception)
    • 由RuntimeException类描述的编程错误
    • 由JVM抛出,如:类型转换失败、数组下标越界、…
  • 异常(Exception)
    • 由Exception类描述,有程序和外部环境引起的错误
    • 能通过程序捕获和处理

也可以将Java的异常分为两类

  1. 必检异常(受检异常)

    在Java代码设计时,如果A方法调用了B方法,要传给B方法参数,但是这个参数B觉得A可能会传错,所以就会在B中检测,如果发现不对,就抛出一个受检异常。那个A在调用B的方法的时候就会被要求强制检查这个异常,防止出错。比如IOException

    必检异常会在方法头中将可能出现的异常抛出:throw xxx

  2. 免检异常

    免检异常一般是错误异常,一但出现这种异常,就表示当前执行的程序无法再执行下去了。比如NullPointException.

异常处理

异常抛出:在发生异常事件处产生一个异常对象,并交给运行环境,称为异常抛出

异常捕获:异常抛出后,从生成异常对象的代码开始,沿着方法调用栈回溯查找,直到找到包含异常处理的方法,称为异常捕获

异常处理的特点

  1. 异常父类可以派生若干子类**,** 捕获异常父类的catch子句可以捕获其子类的异常对象。
  2. 对于RuntimeException及其子类,尽量不使用try-catch,而是使用代码检测来避免异常发生。如:使用代码判断一个对象是否为空

自定义异常类

  • Java提供了很多异常类, 应该尽量使用它们而不要创建自己的异常类
  • 如果遇到预定义的异常类不能恰当描述错误信息时,可以创建自定义异常类
  • 通常通过继承Exception类或除RuntimeException外的子类来创建自定义的异常类

第八讲

File类

java.io.File类描述一个目录或文件

常用方法(均为public的实例方法):

  • boolean exists( ),判断描述的文件或目录是否存在
  • boolean isFile( ),判断描述的目标是否是文件
  • boolean isDirectory( ),判断描述的目标是否是目录
  • String getName( ),返回文件或目录的名称
  • String getPath( ),返回文件或目录的路径名
  • boolean canRead( ),判断是否可读
  • boolean canWrite( ),判断是否可写
  • boolean isHidden( ),判断是否隐藏
  • long lastModified( ),返回此文件最后一次被修改的时间
  • long length( ),返回文件的长度,字节为单位
  • File[] listFiles( ),返回目录中所有文件和子目录组成的数组
  • boolean mkdir( ),创建文件夹
  • boolean delete( ),删除文件或目录

I/O流

以字节为传输单位而创建的流称为字节流

以字符为传输单位而创建的流称为字符流

  • 流操作的所有方法都声明抛出java.io.IOException或其后代类
字节流
  • 抽象类InputStream是所有字节输入流的父类
  • 抽象类OutputStream是所有字节输出流的父类

FileInputStream以字节方式从文件读取数据

FileOutputStream以字节方式向文件写入数据

// 以文件字节流实现文件复制
public static void copyAutoClose(File src, File tar) {
    try {
        FileInputStream srcStream = new FileInputStream(src);
        FileOutputStream desStream = new FileOutputStream(tar);
        int data;
        while ((data = srcStream.read()) != -1) {
            desStream.write(data);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
字符流
  • 抽象类Reader是所有字符输入流的父类
  • 抽象类Writer是所有字符输出流的父类

FileReader以字符方式从文件读取数据

FileWriter以字符方法向文件写入数据

// 使用FileReader统计一个文本文件的行数
public void countLines(File txtFile) {
    int numberOfLines = 0;
    try {
        FileReader reader = new FileReader(txtFile);
        int c;
        while ((c = reader.read()) != -1) {
            System.out.print((char) c); // reader.read() 返回的字符的ASCII码
            if (c == '\n') {
                numberOfLines++;
            }
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
缓冲流

缓冲流是高级流,高级流对象必须建立在字节流或字符流之上

对象序列化

  • java.io.Serializable是标记性接口,实现时无需重写方法
  • 一个类实现了java.io.Serializable接口,则该类的对象就是
  • 一个类实现了java.io.Serializable接口,该类的对象组成的数组也是可序列化对象
  • 使用ObjectOutputStreamObjectInputStream能够对可序列化对象进行读写操作

第九讲

泛型

泛型是指数据的类型参数化

定义泛型的语法

  • 泛型类

    [修饰符] class 类名<T>

  • 泛型接口

    [修饰符] interface 接口名<T>

  • 泛型方法

    [修饰符] [static] <T> 返回类型 方法名(T参数)

T 是泛型中的类型参数,在类、接口或方法中定义类型参数T后,就可以在相应的各个语句部分中使用参数T。

泛型的使用

创建泛型对象时不传递类型,默认是Object

ArrayStack stackObject = new ArrayStack();

创建泛型对象时传递类型,栈内元素中指定类及其子类类型

ArrayStack<String> stack1 = new ArrayStack<String>();

ArrayStack<Number> stack2 = new ArrayStack<Number>();

JDK7开始可以省略构造方法后面<>中的类
ArrayStack<String> stack1 = new ArrayStack<>();

ArrayStack<Number> stack2 = new ArrayStack<>();

限制泛型的可用类型

定义泛型时,默认情况下任何类型都可以传递给泛型参数,如果要限制传入的类型,可以使用以下语法:

<T extends 类> 传入类型是指定类或其子类

<T extends 接口> 传入类型是指定实现了该接口的类

通配符

通配符 ?用于声明泛型类变量或方法形参,主要有三种用法:

无边界的通配符:泛型类名<?> var

// 使用无边界通配符可接受Demo<任何类型>类型的实参
public static void output(Demo<?> d){}

固定上边界的通配符:泛型类名<? extends T> var

// 使用上边界通配符可接受Demo<类型及其后代>类型的实参
public static void output(Demo<? extends Number> d){}

固定下边界的通配符:泛型类名<? super T> var

// 使用下边界通配符可接受Demo<类型及其祖先类>类型的实参
public static void output(Demo<? super Number> d){}

说明:T可用是任何类或接口


泛型类变量声明时不使用通配符直接确定类型,只能接收特定类型泛型参数的泛型对象,例如:

// 只接受Demo<Number>类型的实参
public static void output(Demo<Number> d){}

继承泛型类和泛型接口

Java定义类时可以继承一个父类或实现多个接口,父类可以是泛型类,接口可以是泛型接口。

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

泛型使用的注意事项

  1. 不能使用泛型的类型参数创建对象
public class Demo<T> {
    T obj = new T();  //错误
}
  1. 不能使用泛型的类型参数创建数组对象
public class Demo<T> {
	T[] arr = new T[个数];//错误
}
  1. 不能在静态(变量、方法、初始化)使用泛型的类型参数
public class Demo<T> {
   static T data; //错误
}
  1. 定义异常类不能使用泛型
public class MyEx<T> extends Exception {} //错误

第十讲

内部类

内部类是定义在其他类中的类

匿名内部类是一种特殊的内部类,没有类名

Java允许在类的内部定义类,称为嵌套类

嵌套类分为两种:

  • 静态嵌套类:使用static修饰

  • 非静态嵌套类:也称为内部类


  1. 一个嵌套类是其外部类的一个成员,其访问权限可以声明为publicprivateprotected

  2. 内部类(即非静态嵌套类)中可以直接访问其外部类的其他成员,即使这些成员使用private修饰

    当内部类与其外部类有同名实例成员时,如成员名data

    • 内部类成员:this.data
    • 外部类成员:外部类名.this.data
  3. 静态嵌套类中不能访问其外部类实例成员;但是可以访问外部类静态成员

  4. 外部类中可以访问其嵌套类的所有成员,即使这些成员使用private修饰

  5. A类中定义的内部类B编译后得到的文件名是A$B.class

匿名内部类

  • 匿名类允许同时定义和实例化一个类
  • 匿名类实际上是一个没有名字的内部类

匿名类表达式由一下几个部分组成:

  • new
  • 匿名类需要实现接口名或需要继承父类名
  • 圆括号,其中包含需要传递给父类构造方法的参数;如果匿名类实现的是接口参数表保持为空
  • 类的主体,{}包围,其中可以声明数据域重写方法
  • ;结尾

匿名内部类要么创建后立即使用,要么使用实现接口的接口变量或者继承的父类类型变量引用

Lambda表达式

当要实现的匿名内部类是一个函数式接口时,可以使用Lambda表达式实现

public class Main {
    // 函数式接口Fn1
    interface Fn1 {
        void read();
    }
    public static void main(String[] args) {
        // Lambda表达式实现匿名内部类
        Fn1 fn1 = () -> {
            System.out.println("test");
        };
        fn1.read();
        // 传统方法实现匿名内部类
        Fn1 fn2 = new Fn1() {
            @Override
            public void read() {
                System.out.println("11");
            }
        };
        fn2.read();
    }
}

第十一讲

线程

  • 新建状态(newborn)

    Thread类或其子类的对象被声明并创建;此时线程对象已经被分配了内存空间和其他资源,并已被初始化,但是该线程尚未被调度。

  • 就绪状态(runnable)

    也称为可运行状态;处于新建状态的线程被启动后,将进入线程队列排队等待CPU资源,此时它已具备了运行的条件,也就是处于就绪状态;一旦轮到它来享用CPU资源时,就可以 脱离创建它的主线程独立开始自己的生命周期了。

  • 执行状态(running)

    当就绪状态的线程被调度并获得CPU资源时,便进入执行状态。该状态表示线程正在执行,该线程已经拥有了对CPU的控制权。当对象被调度执行时,它将自动调用本对象的run( )方法,从该方法的第一条语句开始执行,一直到执行完毕,除非该线程主动让出CPU的控制权或者CPU的控制权被优先级更高的线程抢占。

    处于执行状态的线程在下列情况下将让出CPU的控制权

    • 线程执行完毕
    • 有比当前线程优先级更高的线程处于就绪状态
    • 线程主动睡眠一段时间
    • 线程在等待某一资源
  • 阻塞状态(blocked)

    一个正在执行的线程如果在某些特殊情况下,将让出CPU并暂时中止自己的执行,线程处于这种不可执行的状态被称为阻塞状态。在这种状态下,即使CPU空闲也不能执行线程。

    使线程进入阻塞状态的几种情况:

    • 调用sleep( )或yield( )方法
    • 为等待一个条件变量,线程调用wait( )方法
    • 该线程与另一线程join( )在一起
  • 消亡状态(dead)

    处于消亡状态的线程不具有继续执行的能力。

    导致线程消亡的原因有两个:

    1. 正常运行的线程完成了它的全部工作,即执行完了run( )方法的最后一条语句并退出
    2. 当进程因故停止运行时,该进程中的所有线程将被强行终止

外记

判断一个年份是否为闰年

boolean isLeapYear=year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

关于包导入的问题

有三种包在JVM运行时会自动被导入:

  1. 当前主类所在的包
  2. java.lang包
  3. 没有名字的包

Scanner类位于java.util包中,它不在上述三种情况中,因此需要导入。
Math类位于java.lang包中,属于情况2,因此无需导入。
自定义的类如果和当前主类位于同一个包下,属于情况1,也无需导入;否则,仍然需要导入。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值