《Java面向对象编程(阿里云大学)》笔记(文档+思维导图)

2023-05-16

课程链接:https://edu.aliyun.com/course/1011
(还是建议去看课程,笔记仅供参考。
由于文中的所有内容均为手敲,并且有些代码并未验证,因此如有错误,烦请指出~ 谢谢~~~
并且请注意:文中的笔记并不完全与视频一致,包括代码与文字描述。)

(思维导图在最后)
————————————————————————————————————

第1章:类与对象

课时1:面向对象简介

  • 面向过程:更多情况下不会做出重用的设计。
  • 面向对象:主要设计形式为模块化设计,可以进行重用配置,更多情况下考虑的是标准,然后根据标准进行拼装。
    • 特征:
      • 封装性:内部的操作对外部不可见(安全);
      • 继承性:在已有结构的基础上继续进行功能的扩充;
      • 多态性:在继承性的基础上扩充而来的概念,指的是类型的转换处理。
    • 步骤:
      • OOA:面向对象分析;
      • OOD:面向对象设计;
      • OOP:面向对象编程。

课时2:类与对象简介

  • 类:对某一类事物共性的抽象概念。
  • 对象:具体的产物。
  • 类是一个模板,而对象是类的实例,所以先有类才有对象。
  • 类的组成:
    • 成员属性(Field):有时简化称为”属性“;
    • 操作方法(Method):定义对象具有的处理行为。

课时3:类与对象的定义及使用

  • 类的定义:class
class Person { // 定义一个类
	String name; // 人的姓名
	int age; // 人的年龄
	public void tell() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
}
  • 语法:
    • 产生对象:
      • 一步骤完成:
        • 声明并实例化对象:类名称 对象名称 = new 类名称();
      • 分步骤完成:
        • 声明对象:类名称 对象名称 = null;
        • 实例化对象:对象名称 = new 类名称();
    • 调用:
      • 类中的属性:对象名称.成员属性
      • 类中的方法:对象名称.方法名称()

课时4:对象内存分析

  • 最常用的内存空间:
    • 堆内存:保存对象的具体信息。
    • 栈内存:保存单块堆内存的地址。即:通过通过地址找到堆内存,而后找到对象内容。

栈内存堆内存

  • 直接声明并实例化对象的内存分析:
public class JavaDemo {
	public static void main(String[] args) {
		Person per = new Person(); // 声明并实例化对象
		per.name = "张三";
		per.age = 18;
		per.tell(); // 进行方法的调用
	}
}

单步骤声明并实例化的内存分析

  • 分步骤声明再实例化对象的内存分析
public class JavaDemo {
	public static void main(String[] args) {
		Person per = null; // 声明对象
		per = new Person(); // 实例化对象
		per.name = "张三";
		per.age = 18;
		per.tell(); // 进行方法的调用
	}
}

分步骤声明再实例化的内存分析

  • 调用只声明但没有实例化的对象
public class JavaDemo {
	public static void main(String[] args) {
		Person per = null; // 声明对象
		per.name = "张三";
		per.age = 18;
		per.tell(); // 进行方法的调用
	}
}

​ 上述代码会产生“java.lang.NullPointerException”的空指针异常,因为此时并没有在堆内存中开辟空间,这种情况只发生于引用数据类型。

课时5:对象引用分析

  • 由于类属于引用数据类型,因此就牵扯到内存的引用传递。
  • 引用传递:指同一块堆内存被不同的栈内存指向。
  • 在主方法中使用引用传递:
public class JavaDemo {
	public static void main(String[] args) {
		Person per1 = new Person(); // 声明并实例化对象
		per1.name = "张三";
		per1.age = 18;
		Person per2 = per1; // 引用传递
		per2.age = 80;
		per1.tell(); // 进行方法的调用
	}
}

主方法引用传递的内存分析

  • 利用方法实现引用传递:
public class JavaDemo {
	public static void main(String[] args) {
		Person per = new Person(); // 声明并实例化对象
		per.name = "张三";
		per.age = 18;
		change(per);
		per.tell(); // 进行方法的调用
	}
	public static void change(Person temp) {
		temp.age = 80;
	}
}

调用方法引用传递的内存分析

课时6:引用与垃圾产生分析

  • 引用传递的本质:一场调戏堆内存的游戏。

  • 引用传递会产生垃圾。

public class JavaDemo {
	public static void main(String[] args) {
		Person per1 = new Person(); // 声明并实例化对象
		Person per2 = new Person();
		per1.name = "张三";
		per1.age = 18;
		per2.name = "李四";
		per2.age = 19;
		per2 = per1; // 引用传递
		per2.age = 80;
		per1.tell(); // 进行方法的调用
	}
}

产生垃圾的内存分析

  • 垃圾空间:没有任何栈内存所指向的堆内存空间,所有的垃圾都将被GC(Garbage Collector:垃圾收集器)不定期进行回收,并且释放无用空间,但如果垃圾过多,一定将影响到GC的处理性能,从而降低整体的程序性能。
  • 一个栈内存只能够保存一个堆内存的地址数据,如果发生更改,则之前的地址数据将从此栈内存中彻底消失。

第2章:深入分析类与对象

课时7:成员属性封装

  • 类的组成就是属性与方法,一般而言方法都是对外提供服务的,所以是不会进行封装处理的;而属性需要较高的安全性,所以就需要封装性对属性进行保护。
  • 对属性进行封装:使用private关键字。
class Person { // 定义一个类
	private String name; // 人的姓名
	private int age; // 人的年龄
	public void tell() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
}
  • 属性一旦封装后,外部将不可以直接进行访问。

  • 【setter、getter】设置或取得属性使用setXxx()、getXxx()方法。以“private String name”为例,

    • 设置属性方法:public void setName(String name) {};
    • 获取属性方法:public String getName() {}。
class Person { // 定义一个类
	private String name; // 人的姓名
	private int age; // 人的年龄
	public void tell() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
	public String getName() {
        return name;
    }
    public void setName(String n) {
        name = n;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int a) {
        age = a;
    }
}
public class JavaDemo {
	public static void main(String[] args) {
		Person per = new Person(); // 声明并实例化对象
		per.setName("张三"); // 在类外部通过方法修改私有属性
		per.setAge(18);
		per.tell(); // 进行方法的调用
	}
}
  • 在定义类时,属性都应该使用private封装,并提供setter、getter方法。

课时8:构造方法与匿名对象

  • 构造方法
    • 作用:实现实例化对象中的属性初始化处理。
    • 使用:通过new关键字进行实现(因为实例化对象就是通过new关键字实现的)。
    • 定义要求:
      • 构造方法名称必须与类名称一致;
      • 构造方法不允许设置返回值类型,即:没有返回值定义;
      • 构造方法时在使用new关键字实例化对象时自动调用的。
class Person { // 定义一个类
	private String name; // 人的姓名
	private int age; // 人的年龄
	public Person(String n, int a) { // 定义有参构造
        name = n; // 为类中的属性赋值(初始化)
        age = a; // 为类中的属性赋值(初始化)
    }
	public void tell() {
		System.out.println("姓名:" + name + ",年龄:" + age);
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		// 1、对象初始化准备
		Person per = new Person("张三", 18); // 声明并实例化对象
		// 2、对象的使用
		per.tell(); // 进行方法的调用
	}
}
  • 实例化对象例子:
    • 对比:
      • 之前:①Person ②per= ③new ④Person()
      • 当前:①Person ②per= ③new ④Person(“张三”, 18)
    • 解析:
      • ①Person:定义对象的所属类型,类型决定了可以调用的方法;
      • ②per=:实例化对象的名称,所有的操作通过对象来进行访问;
      • ③new:开辟一块新的堆内存空间;
      • ④Person():调用无参构造;
      • ④Person(“张三”, 18):调用有参构造。
  • 在Java程序里面考虑到程序结构的完整性,所以所有的类都会提供有构造方法
    • 如果你的类中没有定义的构造方法,那么程序会默认提供无参的构造方法,这个是在程序编译时自动创建的;
    • 如果你的类中有定义构造方法,那么程序则不会自动提供无参的构造方法。
    • 结论:一个类至少存在有一个构造方法,且永恒存在。
疑问:为什么构造方法上不允许设置返回值类型?
	程序编译器是根据代码结构来进行编译处理的,执行时也是根据代码结构来处理的。
	因此如果设置了返回值类型,那么构造方法的结构与普通方法的结构完全相同,这样编译器会认为此方法时一个普通方法。
	普通方法与构造方法最大的区别:构造方法是在类对象实例化时调用的,而普通方法是在类对象实例化产生之后才可以调用的。
  • 匿名对象:直接实例化对象来进行类的操作(没有声明对象,所以对象是没有名字的)。
public class JavaDemo { // 主类
	public static void main(String[] args) {
		new Person("张三", 10).tell(); // 通过匿名对象直接进行方法的调用
	}
}
  • 通过匿名对象调用方法,由于此对象没有任何的引用名称,因此该对象在使用一次之后就成为了垃圾,而所有的垃圾都将被GC进行回收与释放。
  • 只要是方法都可以传递任意的数据类型(基本数据类型、引用数据类型)。

第3章:this关键字

课时9:this调用本类属性

  • 使用this的三类结构:

    • 指代当前类中的属性:this.属性名;
    • 指代当前类中的方法:
      • 构造方法:this();
      • 普通方法:this.方法名();
    • 描述当前对象;
  • 在Java程序中,“{}”是作为一个结构体的边界符,所以在程序里面当进行变量(参数 / 属性)使用时,都会以“{}”作为查找边界。

class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
        this.name = name;
        this.age = age
    }
	public void tell() {
		System.out.println("姓名:" + this.name + ",年龄:" + this.age);
	}
}

课时10:this调用本类方法

  • this实现方法的调用:
    • 构造方法调用(this()):使用new关键字实例化对象时才会调用构造方法;
    • 普通方法调用(this.方法名()):实例化对象产生之后才可以调用普通方法。
class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
        this.setName(name); // 调用本类方法
        setAge(age); // 加不加“this.”都表示调用本类方法
    }
	public void tell() {
		System.out.println("姓名:" + this.name + ",年龄:" + this.age);
	}
	public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
	public String getName() {
        return this.name;
    }
    public int getAge() {
        return this.age;
    }
}
  • 对于构造方法的调用,一定要放在构造方法中执行。
class Person {
	private String name;
	private int age;
	public Person() {
		System.out.println("*** 一个新的Person类对象实例化了。");
	}
	public Person(String name) {
		this(); // 调用本类无参构造
		this.name = name;
	}
	public Person(String name, int age) {
		this(name); // 调用本类单参构造
		this.age = age;
	}
	public void tell() {
		System.out.println("姓名:" + this.name + ",年龄:" + this.age);
	}
	// setter、getter略
}
  • 评价代码的好坏:

    • 代码结构可以重用,提供的时一个中间独立的支持;
    • 我们的目标是:没有重复。
  • 本类构造方法的互相调用

    • 需要注意:
      • 构造方法必须在实例化新对象时调用,所以“this()”语句只允许放在构造方法首行;
      • 狗仔方法互相调用时请保留有程序的出口(避免死循环)。
    • 案例:
      • 要求定义一个描述有员工信息的程序类,该类中提供:编号、姓名、部门、工资,并提供四个构造方法。
        • 【无参构造】编号定义为1000,姓名定义为”无名氏“;
        • 【单参构造】传递编号,姓名定义为”新员工“,部门定义为”未定“,工资为0.0;
        • 【三参构造】传递编号、姓名、部门,工资为2500.00;
        • 【四参构造】所有的属性全部进行传递。
class Emp {
	private long empno; // 编号
	private String ename; // 姓名
	private String dept; // 部门
	private double salary; // 工资
	public Emp() {
		this(1000, "无名氏", null, 0.0);
	}
	public Emp(long empno) {
		this(empno, "新员工", "未定", 0.0);
	}
	public Emp(long empno, String ename, String dept) {
		this(empno, ename, dept, 2500.00);
	}
	public Emp(long empno, String ename, String dept, double salary) {
		this.empno = empno;
		this.ename = ename;
		this.dept = dept;
		this.salary = salary;
	}
	// setter、getter略
	public String getInfo() {
		return "编号:" + this.empno +
				",姓名:" + this.ename +
				",部门:" + this.dept +
				",工资:" + this.salary;
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		Emp emp = new Emp(7369L, "史密斯", "财务部");
		System.out.println(emp.getInfo());
	}
}

课时11:综合实战:简单Java类

  • 简单java类
    • 定义:描述某一类信息的程序类,如描述一个人、描述一本书,并且类中没有特别复杂的逻辑操作,只作为一种信息存储的媒介存在。
    • 核心开发结构:
      • 类名称要有意义,可以明确描述某一类事物;
      • 类中的属性都必须使用private进行封装,并且封装后的属性必须提供setter、getter方法;
      • 类中必须保留有无参的构造方法;
      • 类中不允许出现任何的输出语句,所有内容的获取必须返回;
      • 【非必须】可以提供有一个获取对象详细信息的方法,可将方法名称定义为”getInfo“。
class Dept { // 类名称可以明确描述出某类事物
	private long deptno;
	private String dname;
	private String loc;
	public Dept() {} // 必须提供无参构造
	public Dept(long deptno, String dname, String loc) {
		this.deptno = deptno;
		this.dname = dname;
		this.loc = loc;
	}
	public void setDeptno(long deptno) {
		this.deptno = deptno;
	}
	public void setDname(String dname) {
		this.dname = dname;
	}
	public void setLoc(String loc) {
		this.loc = loc;
	}
	public String getInfo() {
		return "部门编号:" + this.deptno +
				",部门名称:" + this.dname + 
				",部门位置:" + this.loc;
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		Dept dept = new Dept(10, "技术部", "北京");
		System.out.println(dept.getInfo());
	}
}

第4章:static关键字

课时12:声明static属性

  • static:是一个关键字,可以定义属性和方法。
  • 在一个类中,所有的属性一旦定义了实际上内容都交由各自的堆内存空间所保存。
  • 传统操作:
class Person {
	private String name;
	private int age;
	String country = "中华民国";
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	// setter、getter略
	public String getInfo() {
		return "姓名:" + this.name +
				",年龄:" + this.age +
				",国家:" + this.coutry;
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		Person perA = new Person("张三", 10);
		Person perB = new Person("李四", 10);
		Person perC = new Person("王五", 11);
		System.out.println(perA.getInfo());
		System.out.println(perB.getInfo());
		System.out.println(perC.getInfo());
	}
}

不使用static属性的内存分析

  • 使用static属性:
class Person {
	private String name;
	private int age;
	static String country = "中华民国"; // 
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	// setter、getter略
	public String getInfo() {
		return "姓名:" + this.name +
				",年龄:" + this.age +
				",国家:" + this.coutry;
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		Person perA = new Person("张三", 10);
		Person perB = new Person("李四", 10);
		Person perC = new Person("王五", 11);
		perA.country = "中华人民共和国";
		System.out.println(perA.getInfo());
		System.out.println(perB.getInfo());
		System.out.println(perC.getInfo());
	}
}

static属性的内存分析

  • 访问static属性需要注意:由于其是一个公共的属性,虽然可以通过对象进行访问,但是最好的做法应该是通过所有对象的最高代表(类)来进行访问。
  • 所以static属性可以由类名称直接调用。
Person.country = "中华人民共和国";
  • 在进行类设计时,首选的一定是非static属性(95%),而在设计公共信息存储时才使用static属性(5%)。
  • 非static属性必须在实例化对象产生之后才可以使用,而static属性可以在没有实例化对象产生前,直接通过类名称进行调用。

课时13:声明static方法

  • static方法
    • 主要特点:可以直接由类名称在没有实例化对象的情况下进行调用。
class Person {
	private String name;
	private int age;
	static String country = "中华民国"; // 
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public void setCountry(String c) { // static方法
		country = c;
	}
	// setter、getter略
	public String getInfo() {
		return "姓名:" + this.name +
				",年龄:" + this.age +
				",国家:" + this.coutry;
	}
}
public class JavaDemo { // 主类
	public static void main(String[] args) {
		Person.setCountry("中华人民共和国");
		Person per = new Person("张三", 10);
		System.out.println(per.getInfo());
	}
}
  • static方法与非static方法:
    • static方法:只允许调用static属性或static方法;
    • 非static方法:允许调用static属性或static方法。
  • 所有static定义的属性或方法,都可以在没有实例化对象的前提下使用;而所有的非static定义的属性或方法,都必须在实例化对象后才可以使用。

课时14:static应用案例

  • 案例1:编写一个程序类,这个类可以实现实例化对象个数的统计,每次创建新的实例化对象都可以实现一个统计操作。
class Book {
	private String title;
	private static int count = 0;
	public Book(String title) {
		this.title = title;
		count ++;
		System.out.println("第" + count + "本图书创建出来了。");
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		new Book("JAVA");
		new Book("JSP");
		new Book("Spring");
	}
}
  • 案例2:实现属性的自动命名处理
class Book {
	private String title;
	private static int count = 0;
	public Book() {
		this("NOTITLE - " + count++);
	}
	public Book(String title) {
		this.title = title;
	}
	public String getTitle() {
		return this.title;
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		System.out.println(new Book("JAVA").getTitle());
		System.out.println(new Book("JSP").getTitle());
		System.out.println(new Book("Spring").getTitle());
		System.out.println(new Book().getTitle()); // 输出 NOTITLE - 0
		System.out.println(new Book().getTitle()); // 输出 NOTITLE - 1
	}
}

第5章:代码块

课时15:普通代码块

  • 代码块:在程序中使用“{}”定义的结构。

  • 分类:

    • 依据:出现位置、定义的关键字
    • 内容:
      • 普通代码块
      • 构造代码块
      • 静态代码块
      • 同步代码块(多线程相关)
  • 普通代码块:

    • 主要特点:定义在一个方法之中的代码块。
public class JavaDemo {
	public static void main(String[] args) {
		if (true) { // 条件一定满足
			int x = 10; // 局部变量
			System.out.println("x=" + x);
		}
		int x = 100; // 全局变量
		System.out.println("x=" + x);
	}
}

​ 按Java程序的开发标准,相同名称的变量是不能够在同一个方法中重复声明的,但由于此时由不同的分界描述。

public class JavaDemo {
	public static void main(String[] args) {
		{ // 普通代码块
			int x = 10; // 局部变量
			System.out.println("x=" + x);
		}
		int x = 100; // 全局变量
		System.out.println("x=" + x);
	}
}
  • 普通代码块可以使得在一个方法之中对一些结构进行拆分,以防止相同变量名称所带来的互相影响。

课时16:构造代码块

class Person {
	public Person() {
		System.out.println("【构造方法】Person类构造方法执行");
	}
	{
		System.out.println("【构造块】Person类构造块执行");
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		new Person();
		new Person();
		new Person();
	}
}
  • 构造块会优先于构造方法执行,并且每次实例化新对象时都会调用构造块及构造方法。

课时17:静态代码块

  • 静态代码块
    • 定义:指用static关键字定义的代码块
    • 情况:
      • 主类中定义静态块
      • 非主类中定义静态块
    • 主要目的:为类中的静态属性初始化。
class Person {
	public Person() {
		System.out.println("【构造方法】Person类构造方法执行");
	}
	static { // 非主类中定义静态块
		System.out.println("【静态块】静态块执行");
	}
	{
		System.out.println("【构造块】Person类构造块执行");
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		new Person();
		new Person();
		new Person();
	}
}
  • 静态代码块会优先于构造块及构造方法先执行,无论实例化多少个对象,静态块都只执行一次。
public class JavaDemo {
	static { // 主类中定义静态块
		System.out.println("******** 程序初始化 ********");
	}
	public static void main(String[] args) {
		System.out.println("——————");
	}
}
  • 静态代码块优先于主方法先执行。

第6章:面向对象案例分析

课时18:案例分析一(Address)

  • 编写并测试一个代表地址的Address类,地址信息由国家、省份、城市、街道、邮编组成,并可以返回完整的地址信息。
class Address {
	private String country;
	private String province;
	private String city;
	private String street;
	private String zipcode;
	public Address() {}
	public Address(String country, String province, String city, String street, String zipcode) {
        this.country = country;
        this.province = province;
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
    public String getInfo() {
    	return "国家:" + this.country + ",省份:" + this.province +
    			",城市:" + this.city + ",街道:" + this.street +
    			",邮编:" + this.zipcode;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }
    public String getCountry() {
        return this.country;
    }
    public String getProvince() {
        return this.province;
    }
    public String getCity() {
        return this.city;
    }
    public String getStreet() {
        return this.street;
    }
    public String getZipcode() {
        return this.zipcode;
    }
}
public class JavaDemo {
	public static void main(String[] args) {
		System.out.println(new Address("中华人民共和国", "北京", "北京", "天安门街道", "10001").getInfo());
	}
}

课时19:案例分析二(Employee)

  • 定义并测试一个代表员工的Employee类。员工属性包括“编号”、“姓名”、“基本薪水”、“薪水增长率”,还包括计算薪水增长额及计算增长后的工资总额的操作方法。
class Employee {
    private long empno;
    private String ename;
    private double salary;
    private double rate;
	public Employee() {}
    public Employee(long empno, String ename, double salary, double rate) {
        this.empno = empno;
        this.ename = ename;
        this.salary = salary;
        this.rate = rate;
    }
    public double salaryIncValue() { // 得到薪水增长额度
    	return this.salary * this.rate;
    }
    public double salaryIncResult() {
    	return this.salary * (1 + this.rate);
    }
    // setter、getter略
    public String getInfo() {
    	return "雇员编号:" + this.empno + ",雇员姓名:" + this.ename +
        		",基本薪水:" + this.salary + ",薪水增长率:" + this.rate;
    }
}
public class JavaDemo {
	public static void main(String[] args) {
		Employee emp = new Employee(7369L, "史密斯", 3000.0, 0.3);
		System.out.println(emp.getInfo());
		System.out.println("工资调整额度:" + emp.salaryIncValue());
		System.out.println("上调后的工资:" + emp.salaryIncResult());
	}
}

课时20:案例分析三(Dog)

  • 设计一个Dog类,有名字、颜色、年龄等属性,定义构造方法来初始化类的这些属性,定义方法输出Dog信息,编写应用程序使用Dog类。
class Dog {
	private String name;
	private String color;
	private int age;
	public Dog() {}
	public Dog(String name, String color, int age) {
		this.name = name;
		this.color = color;
		this.age = age;
	}
	// setter、getter略
	public String getInfo() {
		return "狗的名字:" + this.name + ",狗的颜色:" + this.color + ",狗的年龄:" + this.age;
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		Dog dog = new Dog("高高", "黑色", 1);
		System.out.println(dog.getInfo());
	}
}

课时21:案例分析四(Account)

  • 构造一个银行账户类,类的构成包括如下内容:
    • 数据成员用户的账户名称、用户的账户余额(private数据类型)
    • 方法包括开户(设置账户名称及余额),利用构造方法完成
    • 查询余额
class Account {
	private String name;
	private double balance;
	public Account() {}
	public Account(String name) {
		this(name, 0.0);
	}
	public Account(String name, double balance) {
		this.name = name;
		this.balance = balance;
	}
	// setter、getter略
	public double getBalance() {
		return this.balance;
	}
	public String getInfo() {
		return "账户名称:" + this.name + ",余额:" + this.balance;
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		Account account = new Account("啊嘟嘟", 9000000.00);
		System.out.println(account.getInfo());
		System.out.println(account.getBalance());
	}
}

课时22:案例分析五(User)

  • 设计一个表示用户的User类,类中的变量由用户名、口令和记录用户个数的变量,定义类的3个构造方法(无参、为用户名赋值、为用户名和口令赋值)、获取和设置口令的方法和返回类信息的方法。
class User {
	private String uid;
	private String password;
	private static int count = 0;
	public User() {
		this("NO-UID", "123456");
	}
	public User(String uid) {
		this(uid, "123456");
	}
	public User(String uid, String password) {
		this.uid = uid;
		this.password = password;
		count ++; // 个数追加
	}
	public static int getCount() { // 获取用户个数
		return count;
	}
	// setter、getter略
	public String getInfo() {
		return "用户名:" + this.uid + ",密码:" + this.password;
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		User userA = new User();
		User userB = new User("小强");
		User userC = new User("大强", "我不行");
		System.out.println(userA.getInfo());
		System.out.println(userB.getInfo());
		System.out.println(userC.getInfo());
		System.out.println("用户个数:" + User.getCount());
	}
}

课时23:案例分析六(Book)

  • 声明一个图书类,其数据成员为书名、编号(利用静态变量实现自动编号)、书价,并拥有静态数据成员册数、记录图书的总册数,在构造方法中利用此静态变量为对象的编号赋值,在主方法中定义多个对象,并求出总册数。
class Book {
	private int bid; // 编号
	private String title; // 书名
	private double price; // 价格
	private static int count = 0;
	public Book(String title, double price) {
		this.bid = count + 1;
		this.title = title;
		this.price = price;
		count ++;
	}
	public static int getCount() { // 获取用户个数
		return count;
	}
	// setter、getter略
	public String getInfo() {
		return "图书编号:" + this.bid + ",图书名称:" + this.title + ",图书价格:" + this.price;
	}
}
public class JavaDemo {
	public static void main(String[] args) {
		Book book1 = new Book("Java", 89.2);
		Book book2 = new Book("Oracle", 79.2);
		System.out.println(book1.getInfo());
		System.out.println(book2.getInfo());
		System.out.println("图书总册数:" + Book.getCount());
	}
}

第7章:数组的定义与使用

课时24:数组的基本定义

  • 数组的本质:一组相关变量的集合。

  • 数组的初始化:

    • 动态初始化(初始化后,数组里每个元素的保存内容都为其对应数据类型的默认值):
      • 声明并初始化数组:
        • 数据类型 数组名称[] = new 数据类型[长度];
        • 数据类型[] 数组名称 = new 数据类型[长度];
    • 静态初始化(在数组定义时就设置好了内容):
      • 简化格式:数据类型[] 数组名称 = {数据1, 数据2, 数据3, …};
      • 完整格式:数据类型[] 数组名称 = new 数据类型[] {数据1, 数据2, 数据3, …};
  • 数组的使用:

    • 数组可以通过脚标进行内容的访问,脚标从0开始定义,所以可使用的脚标范围为:“0 ~ 数组长度-1”,当使用时超过了数组的脚标范围,则会出现“ArrayIndexOutOfBoundsException”的异常。
    • 获取数据的长度可使用“数据名称.length"。
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[3]; // 动态初始化数组
		for (int i = 0; i < data.length; i++) {
            System.out.println(data[i]);
        }
	}
}
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[3]; // 动态初始化数组
		data[0] = 11; // 为数组设置内容
		data[1] = 23; // 为数组设置内容
		data[2] = 56; // 为数组设置内容
		for (int i = 0; i < data.length; i++) {
            System.out.println(data[i]);
        }
	}
}
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[] {11, 23, 56}; // 静态初始化数组
		for (int i = 0; i < data.length; i++) {
            System.out.println(data[i]);
        }
	}
}

课时25:数组引用传递分析

  • 在数组的使用过程中,仍然需要new关键字进行内存空间的开辟,因此这里一定存在有内存关系的匹配问题。
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[3]; // 动态初始化数组
		data[0] = 10; // 为数组设置内容
		data[1] = 20; // 为数组设置内容
		data[2] = 30; // 为数组设置内容
		for (int i = 0; i < data.length; i ++) {
            System.out.println(data[i]);
        }
	}
}

动态初始化数组并赋值的内存分析

public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[] {10, 20, 30}; // 静态初始化数组
		int temp[] = data; // 引用传递
		temp[0] = 99;
		for (int i = 0; i < data.length; i ++) {
            System.out.println(data[i]);
        }
	}
}

静态初始化数组的内存分析

  • 由于数组为引用数组类型,因此同样需要开辟堆内存空间后才可以使用,而如果使用了未开辟堆内存空间的数组,则一定会出现”NullPointerException“的异常。
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = null; // 未开辟堆内存空间
        System.out.println(data[0]); // 出现”NullPointerException“的异常
	}
}

课时26:foreach输出

  • 在JDK1.5之后,为了减轻数组下标对程序的影响(如果下标处理不当会出现数组越界异常),因此参考了.NET中的设计,引入了一个增强型for循环(foreach)。利用foreach的语法结构可以直接自动获取数组中的每一个元素,避免了下标的访问。
  • foreach语法结构:for (数据类型 变量 : 数组 | 集合) {}
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[] {1, 2, 3, 4, 5};
		for (int temp : data) { // 自动循环,将data数组的每一个元素交给temp变量
			System.out.println(temp);
		}
	}
}

课时27:二维数组

  • 二维数组的定义:需要通过两个下标(行下标 + 列下标)来共同定义的数组。

  • 二维数组的初始化:

    • 动态初始化:

      • 数据类型[][] 数组名称 = new 数据类型[行个数][列个数];
        
    • 静态初始化:

      • 数据类型[][] 数组名称 = new 数据类型[][] {{数组,数据,...},{数组,数据,...},...};
        
public class ArrayDemo{
	public static void main(String[] args) {
		int[][] data = new int[][] {
			{1,2,3,4,5}, {1,2,3}, {5,6,7,8}
		};
		for (int i = 0; i < data.length; i ++) { // 传统for循环
			for (int j = 0; j < data[i].length; j ++) {
				System.out.println("data[" + i + "][" + j + "] = " + data[i][j]);
			}
			System.out.println(); // 换行
		}
	}
}
索引(下标)01234
012345
1123
05678
public class ArrayDemo{
	public static void main(String[] args) {
		int[][] data = new int[][] {
			{1,2,3,4,5}, {1,2,3}, {5,6,7,8}
		};
		for (int[] arr : data) { // foreach循环
			for (int num : arr) {
				System.out.print(num + " ");
			}
			System.out.println(); // 换行
		}
	}
}
  • 二维数组就是数组的嵌套使用。

课时28:数组与方法

  • 对于引用数据类型而言,主要的特点就是可以与方法进行引用传递,而由于数组也属于引用数据类型,所以自然也可以通过方法进行引用传递的工作。

  • 通过方法来接收一个数组

public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[] {1,2,3,4,5};
		printArray(data);
	}
	public static void printArray(int[] arr) { // 接收一个int型的数组
		for (int temp : arr) {
			System.out.print(temp + " ");
		}
		System.out.println();
	}
}

接收数组的内存分析

  • 通过方法来返回一个数组
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = initArray();
		printArray(data);
	}
	public static int[] initArray() { //返回一个int型的数组
		int[] arr = new int[] {1,2,3,4,5};
		return arr;
	}
	public static void printArray(int[] arr) { // 接收一个int型的数组
		for (int temp : arr) {
			System.out.print(temp + " ");
		}
		System.out.println();
	}
}

返回数组的内存分析

  • 通过方法来接收数组并改变数组内容
public class ArrayDemo{
	public static void main(String[] args) {
		int[] data = new int[] {1,2,3,4,5};
		changeArray(data);
		printArray(data);
	}
	public static void changeArray(int[] arr) { //返回一个int型的数组
		for (int i = 0; i < arr.length; i ++) {
			arr[i] *= 2; // 改变数组内容
		}
	}
	public static void printArray(int[] arr) { // 接收一个int型的数组
		for (int temp : arr) {
			System.out.print(temp + " ");
		}
		System.out.println();
	}
}

改变数组内容的内存分析

  • 案例:随意定义一个int数组,要求可以计算出这个数组元素的总和、最大值、最小值、平均值。
class ArrayUtil { // 操作工具的类
	private int sum; // 保存总和
	private double avg; // 保存平均值
	private int max; // 保存最大值
	private int min; // 保存最小值
	public ArrayUtil(int[] arr) { // 进行数组计算
		this.max = arr[0]; // 假设第一个是最大值
		this.min = arr[0]; // 假设第一个是最小值
		for (int i = 0; i < arr.length; i ++) {
			if (arr[i] > this.max) this.max = arr[i]; 
			if (arr[i] < this.min) this.min = arr[i]; 
			this.sum += arr[i];
		}
		this.avg = this.sum / arr.length;
	}
	public int getSum() {
		return this.sum;
	}
	public double getAvg() {
		return this.avg;
	}
	public int getMax() {
		return this.max;
	}
	public int getMin() {
		return this.min;
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		int[] data = new int[] {1,2,3,4,5};
		ArrayUtil util = new ArrayUtil(data); // 数据计算
		System.out.println("数组内容的总和:" + util.getSum());
		System.out.println("数组内容的平均值:" + util.getAvg());
		System.out.println("数组内容的最大值:" + util.getMax());
		System.out.println("数组内容的最小值:" + util.getMin());
	}
}
  • 主方法所在的类被称为主类,主类最好不要涉及过于复杂的功能,其相当于是一个客户端,而对于客户端的代码,应该尽量简单。

课时29:数组排序案例分析

  • 数组排序的定义:指将一个杂乱的数组按照顺序进行码放。

数组排序分析

class ArrayUtil {
	// 进行数组的排序处理
	public static void sort(int[] arr) {
		for (int i = 0; i < arr.length; i ++) {
            for (int j = 0; j < arr.length-i-1; j ++) {
				if (arr[j] > arr[j + 1]) { // 交换数据
					int temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
            }
		}
	}
	// 进行数组的打印
	public static void print(int[] arr) {
		for (int num : arr) {
			System.out.print(num + " ");
		}
		System.out.println();
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		int[] arr = new int[] {8,9,0,2,3,5,10,7,6,1};
		ArrayUtil.sort(arr); // 排序
		ArrayUtil.print(arr); // 打印
	}
}
  • 在进行类设计时,如果发现类中没有属性存在的意义,那么定义的方法就没有必要使用普通方法了,因为普通方法需要在实例化对象之后才可以调用。

课时30:数组转置案例分析

  • 数组转置的定义:指进行数据内容前后转置的处理,即:首尾交换。

    • 数组转置前的内容:1,2,3,4,5,6,7,8,9
    • 数组转置后的内容:9,8,7,6,5,4,3,2,1
  • 数组转置的做法:

    • 做法一:定义一个新的数组,然后按照逆序的方式保存(会产生无用的垃圾空间)
      数组转置做法一

      public class ArrayDemo {
      	public static void main(String[] args) {
      		int[] data = new int[] {1,2,3,4,5,6,7,8,9};
      		int[] temp = new int[data.length]; // 用于保存转置后内容的数组,第二个数组
      		int foot = temp.length - 1; // 第二个数组的脚标
      		for (int i = 0; i < data.length; i ++) {
      			temp[foot --] = data[i];
      		}
      		data = temp;
      		ArrayUtil.print(data); // 打印
      	}
      }
      

      数组转置做法一的内存分析

    • 做法二:在一个数组上进行转置

      数组转置做法二

      public class ArrayDemo {
      	public static void main(String[] args) {
      		int[] data = new int[] {1,2,3,4,5,6,7,8,9};
      		int center = data.length / 2; // 确定转换次数
      		int head = 0; // 操作头脚标
      		int tail = data.length - 1; // 操作尾脚标
      		for (int i = 0; i < center; i ++) {
      			int temp = data[head];
      			data[head] = data[tail];
      			data[tail] = temp;
      			head ++;
      			tail --;
      		}
      		ArrayUtil.print(data); // 打印
      	}
      }
      
    • 两种做法的对比:

      • 做法一:循环次数较多,还会产生垃圾;
      • 做法二:循环次数降低,存在if判断,增加了时间复杂度,但可以减少无用对象的产生,以提升性能。
class ArrayUtil {
	// 进行数组的转置处理
	public static void reverse(int arr[]) {
		int center = arr.length / 2; // 确定转换次数
		int head = 0; // 操作头脚标
		int tail = arr.length - 1; // 操作尾脚标
		for (int i = 0; i < center; i ++) {
			int temp = arr[head];
			arr[head] = arr[tail];
			arr[tail] = temp;
			head ++;
			tail --;
		}
	}
	// 进行数组的打印
	public static void print(int arr[]) {
		for (int num : arr) {
			System.out.print(num + " ");
		}
		System.out.println();
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		int[] arr = new int[] {1,2,3,4,5,6,7,8,9};
		ArrayUtil.reverse(arr); // 转置
		ArrayUtil.print(arr); // 打印
	}
}

课时31:数组相关类库

  • 数组排序:java.util.Arrays.sort(数组名称);
public class ArrayDemo {
	public static void main(String[] args) {
		int[] arr = new int[] {23,12,1,234,2,6,12,34,56};
		java.util.Arrays.sort(arr);
		ArrayUtil.print(arr); // 输出 1 2 6 12 12 23 34 56 234 
	}
}
  • 数组拷贝:System.arraycopy(源数组, 源数组开始下标, 目标数组, 目标数组开始下标, 拷贝长度);
public class ArrayDemo {
	public static void main(String[] args) {
		int[] arr1 = new int[] {1,2,3,4,5,6,7,8,9};
		int[] arr2 = new int[] {11,22,33,44,55,66,77,88,99};
		System.arraycopy(arr1, 5, arr2, 3, 3); // 使用系统函数
		ArrayUtil.print(arr2); // 输出 11 22 33 6 7 8 77 88 99 
	}
}
class ArrayUtil {
	// 进行数组的拷贝处理
	public static void copy(int[] srcArr, int srcIndex, int[] dscArr, int dscIndex, int len) {
		for (int i = 0; i < len; i ++) {
			dscArr[dscIndex ++] = srcArr[srcIndex ++];
		}
	}
	// 进行数组的打印
	public static void print(int[] arr) {
		for (int num : arr) {
			System.out.print(num + " ");
		}
		System.out.println();
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		int[] arr1 = new int[] {1,2,3,4,5,6,7,8,9};
		int[] arr2 = new int[] {11,22,33,44,55,66,77,88,99};
		ArrayUtil.copy(arr1, 5, arr2, 3, 3); // 使用自定义函数
		ArrayUtil.print(arr2); // 输出 11 22 33 6 7 8 77 88 99
	}
}

课时32:方法可变参数

  • 从JDK1.5开始,为了方便开发者进行可变参数的定义,对于方法的参数则有了新的支持。
class ArrayUtil {
	public static int sum(int ... data) { // 变种数组
		int sum = 0;
		for (int temp : data) {
			sum += temp;
		}
		return sum;
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		System.out.println(ArrayUtil.sum(1,2,3));
		System.out.println(ArrayUtil.sum(new int[] {1,2,3}));
	}
}
  • 可变参数的本质:仍然还是数组。

课时33:对象数组

  • 对象数组的定义:由类作为内容的数组。
  • 对象数组的初始化:
    • 动态初始化:类[] 对象数组名称 = new 类[长度]; (此时每个元素的内容都是null)
    • 静态初始化:类[] 对象数组名称 = new 类 {实例化对象, 实例化对象, …};
class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getInfo() {
		return "姓名:" + this.name + ",年龄:" + this.age;
	}
	// setter、getter略
}
public class ArrayDemo {
	public static void main(String[] args) {
		Person[] person = new Person[3]; // 对象数组,动态初始化
		for (int i = 0; i < person.length; i ++) {
			System.out.println(person[i]); // 输出3行null
		}
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		Person[] person = new Person[3]; // 对象数组,动态初始化
		person[0] = new Person("张三", 20);
		person[1] = new Person("李四", 20);
		person[2] = new Person("王五", 20);
		for (int i = 0; i < person.length; i ++) {
			System.out.println(person[i]); // 输出3行内存地址
		}
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		Person[] person = new Person[3]; // 对象数组,动态初始化
		person[0] = new Person("张三", 20);
		person[1] = new Person("李四", 20);
		person[2] = new Person("王五", 20);
		for (int i = 0; i < person.length; i ++) {
			System.out.println(person[i]); // 正常输出3行姓名和年龄
		}
	}
}

对象数组动态初始化的内存分析

public class ArrayDemo {
	public static void main(String[] args) {
		Person[] person = new Person[] { // 对象数组,静态初始化
			new Person("张三", 20),
			new Person("李四", 20),
			new Person("王五", 20),
		};
		for (int i = 0; i < person.length; i ++) {
			System.out.println(person[i]); // 正常输出3行姓名和年龄
		}
	}
}

第8章:引用传递实际应用

课时34:类关联结构

  • 案例:一个人有一辆车,一辆车属于一个人。
class Car {
	private String name;
	private double price;
	private Person person; // 车应该属于一个人
	public Car(String name, double price) {
		this.name = name;
		this.price = price;
	}
	public void setPerson(Person person) {
		this.person = person;
	}
	public Person getPerson() {
		return this.person;
	}
	public String getInfo() {
		return "汽车品牌型号:" + this.name + ",汽车价值:" + this.price;
	}
}
class Person {
	private String name;
	private int age;
	private Car car; // 人有一辆车
	public Person(String name, int age) {
		this.name = name ;
		this.age = age;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	public Car getCar() {
		return this.car;
	}
	public String getInfo() {
		return "姓名:" + this.name + ",年龄:" + this.age;
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		// 第一步:声明对象并设置彼此的关系
		Person person = new Person("林强", 29);
		Car car = new Car("宾利", 8000000.00);
		person.setCar(car); // 一个人有一辆车
		car.setPerson(person); // 一辆车属于一个人
		// 第二步:根据关系获取数据
		System.out.println(person.getCar().getInfo()); // 代码链
		System.out.println(car.getPerson().getInfo());
	}
}

课时35:自身关联

  • 案例:一个人有多个孩子。

在这里插入图片描述

class Car {
	private String name;
	private double price;
	private Person person; // 车应该属于一个人
	public Car(String name, double price) {
		this.name = name;
		this.price = price;
	}
	public void setPerson(Person person) {
		this.person = person;
	}
	public Person getPerson() {
		return this.person;
	}
	public String getInfo() {
		return "汽车品牌型号:" + this.name + ",汽车价值:" + this.price;
	}
}
class Person {
	private String name;
	private int age;
	private Car car; // 人有一辆车
	private Person[] children; // 有多个孩子
	public Person(String name, int age) {
		this.name = name ;
		this.age = age;
	}
	public void setChildren(Person[] children) {
        this.children = children;
    }
    public Person[] getChildren() {
        return this.children;
    }
	public void setCar(Car car) {
		this.car = car;
	}
	public Car getCar() {
		return this.car;
	}
	public String getInfo() {
		return "姓名:" + this.name + ",年龄:" + this.age;
	}
}
public class ArrayDemo {
	public static void main(String[] args) {
		// 第一步:声明对象并设置彼此的关系
		Person person = new Person("林强", 29);
		Person childA = new Person("吴硕", 18);
		Person childB = new Person("郭仁义", 19);
		childA.setCar(new Car("宝马", 300000.00)); // 匿名Car对象
		childB.setCar(new Car("法拉利", 1500000.00)); // 匿名Car对象
		person.setChildren(new Person[] {childA, childB}); // 一个人有多个孩子
		Car car = new Car("宾利", 8000000.00);
		person.setCar(car); // 一个人有一辆车
		car.setPerson(person); // 一辆车属于一个人
		// 第二步:根据关系获取数据
		System.out.println(person.getCar().getInfo()); // 代码链
		System.out.println(car.getPerson().getInfo());
		System.out.println("—————— 我是一条分界线 ——————");
		// 根据人找到所有的孩子以及孩子对应的汽车
		Person[] children = person.getChildren();
		for (int i = 0; i < person.getChildren().length; i ++) {
			Person child = children[i];
			System.out.println("\t" + child.getInfo());
			System.out.println("\t\t" + child.getCar().getInfo());
		}
	}
}

课时36:合成设计模式

  • 合成设计模式:将事物拆分后再重新组合的设计模式。
  • 案例:定义一个可以描述电脑组成的类,电脑拆分为:显示器、主机,而主机包括一系列硬件。
class Computer {
	private Display[] displays;
	private Host host;
}
class Display {}
class Host {
	private Mainboard mainboard;
	private Keyword keyword;
	private Mouse mouse;
}
class Mainboard {
	private Memory[] memorys;
	private Cpu[] cpus;
	private Gpu gpu;
	private Hdd[] hdd;
}
class Keyword {}
class Mouse {}
class Memory {}

第9章:数据表与简单Java类映射转换

课时37:综合实战:数据表与简单Java类映射转换

  • 程序类的定义实际上和数据表的定义相差不大。

  • 数据表与简单Java类之间的基本映射关系:

    • 表设计 = 类的定义;
    • 表中的字段 = 类的成员属性;
    • 表的外键关联 = 引用关联;
    • 表的一行记录 = 类的一个实例化对象;
    • 表的多行记录 = 对象数组。
  • 案例:部门表与雇员表

    部门表与雇员表

    • 表的分析:

      • 一个部门有多个雇员;
      • 一个雇员属于一个部门;
      • 一个雇员有一个领导;
    • 程序的分析:

      • 根据部门信息可获取:
        • 一个部门的信息;
        • 一个部门之中所有雇员的信息;
        • 一个雇员对应的领导的信息;
      • 根据雇员信息可获取:
        • 一个雇员所在部门的信息;
        • 一个雇员对应领导的信息
    • 实现步骤:

      • 第一步:分别定义Dept、Emp两个实体类

        class Dept {
        	private long deptno;
        	private String dname;
        	private String loc;
        	public Dept(long deptno, String dname, String loc) {
        		this.deptno = deptno;
        		this.dname = dname;
        		this.loc = loc;
        	}
        	// setter、getter、无参构造略
        	public String getInfo() {
        		return "【部门信息】部门编号:" + this.deptno +
        				",部门名称:" + this.dname +
        				",部门位置:" + this.loc;
        	}
        }
        class Emp {
        	private long empno;
        	private String ename;
        	private String job;
        	private double sal;
        	private double comm;
        	public Emp(long empno, String ename, String job, double sal, double comm) {
        		this.empno = empno;
        		this.ename = ename;
        		this.job = job;
        		this.sal = sal;
        		this.comm = comm;
        	}
        	// setter、getter、无参构造略
        	public String getInfo() {
        		return "【雇员信息】雇员编号:" + this.empno +
        				",雇员姓名:" + this.ename +
        				",雇员职位:" + this.job +
        				",基本工资:" + this.sal +
        				",佣金:" + this.comm;
        	}
        }
        
      • 第二步:配置所有的关联字段

        class Dept {
        	private Emp[] emps;
        	public void setEmps(Emp[] emps) {
        		this.emps = emps;
        	}
        	public Emp[] getEmps() {
        		return this.emps;
        	}
        }
        class Emp {
        	private Dept dept; // 所属部门
        	private Emp mgr; // 所属领导
        	public void setDept(Dept dept) {
        		this.dept = dept;
        	}
        	public Dept getDept() {
        		return this.dept;
        	}
        	public void setMgr(Emp mgr) {
        		this.mgr = mgr;
        	}
        	public Emp getMgr() {
        		return this.mgr;
        	}
        }
        
    • 实际实现:

      public class ArrayDemo {
      	public static void main(String[] args) {
      		// 第一步:根据关系进行类的定义
      		Dept dept = new Dept(10, "财务部", "上海");
      		Emp empA = new Emp(7369L, "SMITH", "CLERK", 800.00, 0.0);
      		Emp empB = new Emp(7566L, "FORD", "MANAGER", 2450.00, 0.0);
      		Emp empC = new Emp(7839L, "KING", "PRESIDENT", 5000.00, 0.0);
      		// 为对象进行关联设置
      		empA.setDept(dept); // 设置雇员与部门的关联
      		empB.setDept(dept); // 设置雇员与部门的关联
      		empC.setDept(dept); // 设置雇员与部门的关联
      		empA.setMgr(empB); // 设置雇员与领导的关联
      		empB.setMgr(empC); // 设置雇员与领导的关联
      		dept.setEmps(new Emp[] {empA, empB, empC}; // 部门与雇员
      		// 第二步:根据关系获取数据
      		System.out.println(dept.getInfo());
      		Emp[] emps = dept.getEmps();
      		for (int i = 0; i < emps.length; i ++) {
      			System.out.println("\t" + emps[i].getInfo());
      			Emp mgr = emps[i].getMgr();
                  if (null != mgr) {
                      System.out.println("\t\t" + mgr.getInfo());
                  }
      		}
      		System.out.println("————————————————");
              System.out.println(empB.getDept().getInfo()); // 根据雇员获取部门信息
              System.out.println(empB.getMgr().getInfo()); // 根据雇员获取领导信息
      	}
      }
      
  • 实际项目开发实现步骤:

    • 第一步:根据表的结构关系进行对象的配置;
    • 第二步:根据要求通过结构获取数据。

课时38:综合实战:一对多映射

分类与子分类

  • 将以上的结构转换为类结构,并可以获取如下信息:
    • 获取一个分类的完整信息;
    • 可以根据分类获取其对应的所有子分类的信息。
class Item {
    private long iid;
    private String title;
    private Subitem[] subitems;
    public Item(long iid, String title) {
        this.iid = iid;
        this.title = title;
    }
    public void setSubitems(Subitem[] subitems) {
        this.subitems = subitems;
    }
    public Subitem[] getSubitems() {
        return this.subitems;
    }
    public String getInfo() {
        return "【分类信息】iid = " + this.iid + ",title = " + this.title;
    }
}
class Subitem {
    private long sid;
    private String title;
    private Item item;
    public Subitem(long sid, String title) {
        this.sid = sid;
        this.title = title;
    }
    public void setItem(Item item) {
        this.item = item;
    }
    public Item getItem() {
        return this.item;
    }
    public String getInfo() {
        return "【子分类信息】sid = " + this.sid + ",title = " + this.title;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        // 第一步:根据结构设置对象数据
        Item item = new Item(1L, "图书");
        Subitem[] subitems = new Subitem[] {
            new Subitem(10L, "编程图书"),
            new Subitem(10L, "图形图像类图书"),
		};
        item.setSubitems(subitems); // 一个分类下有多个子分类
        for (int i = 0; i < subitems.length; i ++) {
            subitems[i].setItem(item);
        }
        // 第二步:根据要求获取数据
        System.out.println(item.getInfo());
        subitems = item.getSubitems();
        for (int i = 0; i < subitems.length; i ++) {
            System.out.println("\t" + subitems[i].getInfo());
        }
    }
}

课时39:综合实战:多对多映射

用户信息与商品与访问记录

  • 将以上的结构转换为类结构(中间的访问记录表不要求转换),并可以获取如下信息:
    • 获取一个用户访问的所有商品的详细信息;
    • 获取一个商品被浏览过的全部的用户信息。
class Member {
	private String mid;
	private String name;
	private Product[] products;
	public Member(String mid, String name) {
		this.mid = mid;
		this.name = name;
	}
	public void setProducts(Product[] products) {
        this.products = products;
    }
    public Product[] getProducts() {
        return this.products;
    }
	public String getInfo() {
		return "【用户信息】mid = " + this.mid + ",name = " + this.name;
	}
}
class Product {
	private long pid;
	private String title;
	private double price;
	private Member[] members;
	public Product(long pid, String title, double price) {
		this.pid = pid;
		this.title = title;
		this.price = price;
	}
	public void setMembers(Member[] members) {
        this.members = members;
    }
    public Member[] getMembers() {
        return this.members;
    }
	public String getInfo() {
		return "【商品信息】pid = " + this.pid + ",title = " + this.title + ",price = " + this.price;
	}
}
public class JavaDemo {
    public static void main(String[] args) {
        // 第一步:根据结构设置对象数据
        Member memA = new Member("pika", "皮卡");
        Member memB = new Member("pikaqiu", "皮卡丘");
        Product proA = new Product(1L, "Java开发图书", 79.8);
        Product proB = new Product(2L, "非常大的耳机", 2309.8);
        Product proC = new Product(3L, "小米手机", 3000.8);
        memA.setProducts(new Product[] {proA, proB, proC});
        memB.setProducts(new Product[] {proA});
        proA.setMembers(new Member[] {memA, memB});
        proB.setMembers(new Member[] {memA});
        // 第二步:根据要求获取数据
        System.out.println("———————— 根据用户查看浏览商品信息 ————————");
        System.out.println(memA.getInfo());
        Product[] memA_products = memA.getProducts();
        for (int i = 0; i < memA_products.length; i ++) {
            System.out.println("\t" + memA_products[i].getInfo());
        }
        System.out.println("———————— 根据商品找到被浏览的记录 ————————");
        System.out.println(proA.getInfo());
        Member[] proA_members = proA.getMembers();
        for (int i = 0; i < proA_members.length; i ++) {
            System.out.println("\t" + proA_members[i].getInfo());
        }
    }
}

课时40:综合实战:复杂多对多映射

  • 在实际的项目开发中,对于用户的授权管理时一项重要的任务,下面给出了一个最为常见的用户权限管理的表结构设计,基本的关系如下:
    • 一个用户可以拥有多个角色,一个角色可能有多个用户;
    • 一个角色可以拥有多个权限。

用户权限管理

  • 要求实现如下查询功能:
    • 可以根据一个用户找到该用户对应的所有角色,以及每一个角色对应的所有权限信息;
    • 可以根据一个角色找到该角色下的所有权限,以及拥有此角色的全部用户信息;
    • 可以根据一个权限找到具备有此权限的所有用户信息。
class Member {
    private String mid;
    private String name;
    private Role[] roles;
    public Member(String mid, String name) {
        this.mid = mid;
        this.name = name;
    }
    public void setRoles(Role[] roles) {
        this.roles = roles;
    }
    public Role[] getRoles() {
        return this.roles;
    }
    public String getInfo() {
        return "【用户信息】mid = " + this.mid + ",name = " + this.name;
    }
}
class Role {
    private long rid;
    private String title;
    private Member[] members;
    private Privilege[] privileges;
    public Role(long rid, String title) {
        this.rid = rid;
        this.title = title;
    }
    public void setMembers(Member[] members) {
        this.members = members;
    }
    public Member[] getMembers() {
        return this.members;
    }
    public void setPrivileges(Privilege[] privileges) {
        this.privileges = privileges;
    }
    public Privilege[] getPrivileges() {
        return this.privileges;
    }
    public String getInfo() {
        return "【角色信息】rid = " + this.rid + ",title = " + this.title;
    }
}
class Privilege {
    private long pid;
    private String title;
    private Role role;
    public Privilege(long pid, String title) {
        this.pid = pid;
        this.title = title;
    }
    public void setRole(Role role) {
        this.role = role;
    }
    public Role getRole() {
        return this.role;
    }
    public String getInfo() {
        return "【权限信息】pid = " + this.pid + ",title = " + this.title;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        // 第一步:根据结构设置对象数据
        Member memA = new Member("pika", "皮卡");
        Member memB = new Member("pikaqiu", "皮卡丘");
        Role roleA = new Role(1L, "系统配置");
        Role roleB = new Role(2L, "备份管理");
        Role roleC = new Role(3L, "认识管理");
        Privilege priA = new Privilege(1000L, "系统初始化");
        Privilege priB = new Privilege(1001L, "系统还原");
        Privilege priC = new Privilege(1002L, "系统环境修改");
        Privilege priD = new Privilege(2000L, "备份员工数据");
        Privilege priE = new Privilege(2001L, "备份部门数据");
        Privilege priF = new Privilege(2002L, "备份公文数据");
        Privilege priG = new Privilege(3000L, "增加员工");
        Privilege priH = new Privilege(3001L, "编辑员工");
        Privilege priI = new Privilege(3002L, "浏览员工");
        Privilege priJ = new Privilege(3003L, "员工离职");
        // 增加角色与权限的对应关系
        roleA.setPrivileges(new Privilege[] {priA, priB, priC});
        roleB.setPrivileges(new Privilege[] {priD, priE, priF});
        roleC.setPrivileges(new Privilege[] {priG, priH, priI, priJ});
        // 增加权限与角色对应关系
        priA.setRole(roleA);
        priB.setRole(roleA);
        priC.setRole(roleA);
        priD.setRole(roleB);
        priE.setRole(roleB);
        priF.setRole(roleB);
        priG.setRole(roleC);
        priH.setRole(roleC);
        priI.setRole(roleC);
        priJ.setRole(roleC);
        // 增加用户与角色的对应关系
        memA.setRoles(new Role[] {roleA, roleB});
        memB.setRoles(new Role[] {roleA, roleB, roleC});
        roleA.setMembers(new Member[] {memA, memB});
        roleB.setMembers(new Member[] {memA, memB});
        roleC.setMembers(new Member[] {memB});
        // 第二步:根据要求获取数据
        System.out.println("———————— 通过用户查找信息 ————————");
        System.out.println(memB.getInfo());
        Role[] memB_roles = memB.getRoles();
        for (int i = 0; i < memB_roles.length; i ++) {
            System.out.println("\t" + memB_roles[i].getInfo());
            Privilege[] memB_roles_pris = memB_roles[i].getPrivileges();
            for (int j = 0; j < memB_roles_pris.length; j ++) {
                System.out.println("\t\t" + memB_roles_pris[j].getInfo());
            }
        }
        System.out.println("———————— 通过角色查找信息 ————————");
        System.out.println(roleB.getInfo());
        System.out.println("\t浏览此角色下的所有权限信息:");
        Privilege[] roleB_pris = roleB.getPrivileges();
        for (int j = 0; j < roleB_pris.length; j ++) {
             System.out.println("\t\t" + roleB_pris[j].getInfo());
        }
        System.out.println("\t浏览此角色下的所有用户信息:");
        Member[] roleB_mems = roleB.getMembers();
        for (int j = 0; j < roleB_mems.length; j ++) {
             System.out.println("\t\t" + roleB_mems[j].getInfo());
        }
        System.out.println("———————— 通过权限查找信息 ————————");
        System.out.println(priB.getInfo());
        Member[] priB_role_mems = priB.getRole().getMembers();
        for (int j = 0; j < priB_role_mems.length; j ++) {
             System.out.println("\t\t" + priB_role_mems[j].getInfo());
        }
    }
}

第10章:String类特点分析

课时41:String类简介

  • 在Java程序里,使用”"“(双引号)对字符串进行定义,利用”+“(加号)来实现字符串的连接。

  • String类的对象实例化

    • 直接赋值

      public class StringDemo {
          public static void main(String[] args) {
              String str = "pikaqiu"; // 直接赋值进行实例化
              System.out.println(str);
      	}
      }
      
    • 构造方法

      public class StringDemo {
      	public static void main(String[] args) {
              String str = new String("pikaqiu"); // 使用构造方法进行实例化
              System.out.println(str);
      	}
      }
      
  • String类之所以能保存字符串的主要原因是其中定义了一个数组,也就是说字符串里的每一个字符都是保存在数组中。

  • JDK1.8与JDK1.9String支持类的对比:

JDK1.8的String支持类JDK1.9的String支持类
JDK1.8的String类.PNGJDK1.9的String类.PNG
  • String类保存的数组类型:

    • JDK1.8:String类保存的是字符数组;

      private final char[] value;
      
    • JDK1.9:String类保存的是字节数组。

      private final byte[] value;
      
  • 字符串就是对数组的一种特殊包装应用。

课时42:字符串比较

public class StringDemo {
	public static void main(String[] args) {
        String strA = "pikaqiu";
        String strB = new String("pikaqiu");
        System.out.println(strA == strB); // 输出 false
        System.out.println(strA.equals(strB)); // 输出 true
	}
}
  • 面试题:请解释String比较中“==”与equals()的区别?
    • “==”:进行的是数值比较,如果用于对象比较上的话,比较的是两个对象的内存地址;
    • equals():是String类提供的一个比较方法,比较的是字符串的内容。

课时43:字符串常量

  • 使用“"”(双引号)来定义的字符串常量,实际上描述的都是一个String类的匿名对象。
public class StringDemo {
    public static void main(String[] args) {
        String str = "pikaqiu";
        System.out.println("pikaqiu".equals(str)); // 输出 true。此处的“pikaqiu”则是字符串常量
	}
}
  • 关于对象相等判断的小技巧:应将字符串常量写在前面,如此可避免变量为null时产生NullPointerException的空指针异常。

课时44:String类对象两种实例化方式比较

  • String类对象的实例化方式:

    • 直接赋值:

      String str = "mldn";
      

      String类直接赋值的实例化方式.PNG

      • String(常量)池

        public class StringDemo {
            public static void main(String[] args) {
                String strA = "mldn";
                String strB = "mldnjava";
                String strC = "mldn";
                System.out.println(strA == strC); // 输出 true(判断的是内存地址)
        	}
        }
        

      String类池.PNG

      在采用直接赋值的处理过程中,对于字符串而言,可以实现池数据的自动保存,这样再有相同数据定义时,可以减少对象的产生,以提升操作性能。

    • 构造方法:

      new String("mldn");
      

      String类构造方法的实例化方式.PNG

      此时会开辟两块堆内存空间,而后只会使用一块,而另一个由于字符串常量所定义的匿名对象将称为垃圾对象。

  • 实例化方式对比:

    String类实例化方式对比.PNG

  • 利用构造方法实例化的对象,会产生专用的内存空间,但在String类中,也提供了手工入池的方法:

    public String intern();
    
    public class StringDemo {
        public static void main(String[] args) {
            String strA = "pikaqiu";
            String strB = new String("pikaqiu").intern(); // 使用intern()手工入池的方法
            System.out.println(strA == strB); // 输出 true
    	}
    }
    
  • 面试题:请解释String类两种对象实例化方式的区别?

    • 直接赋值:只会产生一个实例化对象,并且其自动被保存在对象池中,以实现该字符串对象的重用;
    • 构造方法:会产生两个实例化对象,并且非字符串常量的对象不会自动入池,无法实现对象重用。但也可以利用intern()方法进行手工入池。

课时45:String类对象常量池

  • 对象池的主要目的:实现数据的共享处理。

  • Java对象(常量)池的分类:

    • 静态常量池:程序(*.class)在加载时,会自动将程序中保存的字符串、普通的常量、类和方法的信息等,全部进行分配。
    • 运行时常量池:当程序(*.class)加载后,给程序中一些变量提供的常量池。
public class StringDemo {
    public static void main(String[] args) {
        String strA = "pikaqiu";
        String strB = "pi" + "ka" + "qiu";
        System.out.println(strA == strB); // 输出 true
	}
}

String常量池.PNG

public class StringDemo {
    public static void main(String[] args) {
    	String info = "ka";
        String strA = "pikaqiu";
        String strB = "pi" + info + "qiu";
        System.out.println(strA == strB); // 输出 false
	}
}

此时,由于info是一个变量,变量的内容是可以修改的,因此程序在加载时,并不能确定info是什么内容,所以strB的最终内容也是不能够确定的。

课时46:字符串修改分析

  • 由于在String类中包含的是一个数组,数组最大的缺点就是长度不可变。当设置了一个字符串之后,会自动进行数组空间的开辟,开辟的空间长度则已经固定了。
public class StringDemo {
    public static void main(String[] args) {
    	String str = "www.";
    	str += "mldn.";
    	str += "cn";
    	System.out.println(str);
	}
}

String不可修改.PNG

  • 通过上述的例子可以发现,字符串常量的内容并没有发生任何的改变,改变的只是String类对象的引用,并且这种改变将有空可能带来大量的垃圾空间。

  • 因此,String类在开发中,不要进行内容的频繁修改。

课时47:主方法组成分析

public static void main(String[] args)
  • public:描述的是一种访问权限。主方法是一切的起点,起点一定是公共的。
  • static:表示可由类直接调用。程序的执行是通过类名称来完成的。
  • void:表示无返回。主方法是一切的起点,起点一旦开始就没有返回的可能。
  • main:这是一个系统定义好的方法名称。
  • String[] args:表示字符串的数组,用于实现程序启动参数的接收。
public class StringDemo {
    public static void main(String[] args) {
    	for (String arg : args) {
    		System.out.println(arg);
    	}
	}
}

打印主方法入参.PNG

  • 程序在执行时可以设置启动参数,参数用空格来分隔。但如参数本身有空格,则必须使用“"”(双引号)来包装。

打印带有空格的主方法入参.PNG

第11章:String类常用方法

课时48:JavaDoc文档简介

  • JavaDoc:Java的API文档。

  • 文档地址(可选择JDK版本):https://docs.oracle.com/en/java/javase/index.html

  • JDK9(JDK1.9)的文档地址:https://docs.oracle.com/javase/9/docs/api/overview-summary.html

  • 在JDK1.9之前,Java中的常用类库都会在JVM启动时进行全部的加载,这样性能会有所下降,因此在JDK1.9之后,提供了模块化设计,将一些程序类放在了不同的模块(Module)里面。

  • String类文档的查找(JDK9):

    java.base.PNG
    java.lang.PNG
    String类的文档查找.PNG

  • 文档结构:

    • 类的完整定义:

      类的完整定义.PNG

    • 类的相关说明信息:

      类的相关说明信息.PNG

    • 成员属性摘要:

      成员属性摘要.PNG

    • 构造方法摘要(“Deprecated”表示不建议使用):

      构造方法摘要.PNG

    • 方法摘要(左边为返回值,右边为方法名称和相应参数):

      方法摘要.PNG

    • 方法的详细说明:

      方法的详细说明.PNG

课时49:字符串与字符

  • String类的方法:

    No方法名称类型描述
    1public String(char[] value)构造将字符数组的全部内容转为字符串
    2public String(char[] value, int offset, int count)构造将字符数组的部分内容转为字符串
    3public char charAt(int index)普通获取指定索引位置的字符
    4public char[] toCharArray()普通将字符串转为字符数组
public class StringDemo {
    public static void main(String[] args) {
    	String str = "pikaqiu";
    	char c = str.charAt(5); // 获取指定索引位置的字符(索引下标从0开始)
    	System.out.println(c); // 输出 i
	}
}
public class StringDemo {
    public static void main(String[] args) {
    	String str = "pikaqiu";
    	char[] arr = str.toCharArray(); // 字符串转字符数组
    	for (char c : arr) {
    		System.out.println(c);
    	}
	}
}
  • 在实际开发中,处理中文时,往往使用char类型,因为其可以包含中文数据。

课时50:字符串与字节

  • 字符串与字节数组之间也可以实现转换的处理操作,但当进行字符串与字节转换时,其主要目的是为了进行二进制的数据传输或进行编码转换。
No方法名称类型描述
1public String(byte[] bytes)构造将字节数组的全部内容转为字符串
2public String(byte[] bytes, int offset, int length)构造将字节数组的部分内容转为字符串
3public byte[] getBytes()普通将字符串转为字节数组
4public byte[] getBytes(String charsetName) throws UnsupportedEncodingException普通编码转换
public class StringDemo {
    public static void main(String[] args) {
    	String str = "pikaqiu";
    	byte[] arr = str.getBytes(); // 字符串转字节数组
    	for (byte b : arr) {
    		System.out.println(c);
    	}
	}
}
  • 字节本身是有长度限制的,一个字节最多可以保存的范围是:-128 ~ 127。

课时51:字符串比较

No方法名称类型描述
1public boolean equals(String anObject)普通区分大小写的相等判断
2public boolean equalsIgnoreCase(String anotherString)普通不区分大小写的相等判断
3public int compareTo(String anotherString)普通区分大小写地进行字符串大小的比较
4public int compareToIgnoreCase(String str)普通不区分大小写地进行字符串大小的比较
public class StringDemo {
    public static void main(String[] args) {
    	String strA = "pikaqiu";
    	String strB = "PIKAQIU";
    	System.out.println(strA.equals(strB)); // 输出 false
    	System.out.println(strA.equalsIgnoreCase(strB)); // 输出 true
	}
}
public class StringDemo {
    public static void main(String[] args) {
    	String strA = "A"; // 字符编码为65
    	String strB = "a"; // 字符编码为97
    	String strC = "b"; // 字符编码为98
    	System.out.println(strA.compareTo(strB)); // 输出 -32
    	System.out.println(strB.compareTo(strA)); // 输出 32
    	System.out.println(strB.compareTo(strC)); // 输出 -1
    	System.out.println("————————————————");
    	System.out.println(strA.compareToIgnoreCase(strB)); // 输出 0
	}
}

课时52:字符串查找

No方法名称类型描述
1public boolean contains(String s)普通判断子字符串是否存在
2public int indexOf(String str)普通由前向后查找指定字符串的位置(找不到返回-1)
3public int indexOf(String str, int fromIndex)普通从指定索引开始查找指定字符串的位置
4public int lastIndexOf(String str)普通由后向前查找指定字符串的位置(找不到返回-1)
5public int lastIndexOf(String str, int fromIndex)普通从指定索引由后向前查找指定字符串的位置
6public boolean startsWith(String prefix)普通判断是否以指定的字符串开头
7public boolean startsWith(String prefix, int toffset)普通从指定索引开始判断是否以指定的字符串开头
8public boolean endsWith(String suffix)普通判断是否以指定的字符串结尾
  • 判断子字符串是否存在

    public class StringDemo {
        public static void main(String[] args) {
        	String str = "pikaqiu";
        	System.out.println(str.contains("pi")); // 输出 true
        	System.out.println(str.contains("pin")); // 输出 false
    	}
    }
    

    contains()方法是JDK1.5后追加的,而在JDK1.5之前,往往只能依靠indexOf()来进行数据的查询。

    public class StringDemo {
        public static void main(String[] args) {
        	String str = "pikaqiu";
        	System.out.println(str.indexOf("pi")); // 输出 0
        	System.out.println(str.indexOf("pin")); // 输出 -1
    	}
    }
    

课时53:字符串替换

No方法名称类型描述
1public String replaceAll(String regex, String replacement)普通替换全部
2public String replaceFirst(String regex, String replacement)普通替换首个
public class StringDemo {
    public static void main(String[] args) {
    	String str = "helloworld";
    	System.out.println(str.replaceAll("l", "X")); // 输出 heXXoworXd
    	System.out.println(str.replaceFirst("l", "X")); // 输出 heXloworld
	}
}

课时54:字符串拆分

No方法名称类型描述
1public String[] split(String regex)普通按指定字符串全部拆分
2public String[] split(String regex, int limit)普通按指定字符串拆分为指定个数
public class StringDemo {
    public static void main(String[] args) {
    	String str = "hello world hello pikaqiu";
    	String[] arr1 = str.split(" "); // 用空格进行全部拆分
    	String[] arr2 = str.split(" ", 2); // 用空格拆分成2个
    	for (String s : arr1) {
    		System.out.println(s);
    	}
    	System.out.println("————————————————");
    	for (String s : arr2) {
    		System.out.println(s);
    	}
	}
}
  • 如果遇到拆分不了的情况,则需要使用“\\”进行转义。

    public class StringDemo {
        public static void main(String[] args) {
        	String str = "192.168.1.1";
        	String[] arr = str.split("\\.");
        	for (String s : arr) {
        		System.out.println(s);
        	}
    	}
    }
    

课时55:字符串截取

  • 截取:从完整的字符串中截取出子字符串。
No方法名称类型描述
1public String substring(int beginIndex)普通从指定索引截取到最后
2public String substring(int beginIndex, int endIndex)普通从指定索引截取到指定索引
public class StringDemo {
    public static void main(String[] args) {
    	String str = "pikaqiu";
    	System.out.println(str.substring(2)); // 输出 kaqiu
    	System.out.println(str.substring(2, 4)); // 输出 ka
	}
}

课时56:字符串格式化

  • 从JDK1.5开始,为了吸引更多的传统开发人员,Java提供了格式化数据的处理操作。类似于C语言中的格式化输出语句,可以利用占位符实现数据的输出。对于占位符而言,常用的有:%s(字符串)、%c(字符)、%d(整数)、%f(小数)。
No方法名称类型描述
1public static String format(String format, 各种类型 … args)普通按指定格式进行字符串的输出
public class StringDemo {
    public static void main(String[] args) {
    	String name = "皮卡丘";
    	int age = 18;
    	double score = 98.987654321;
    	String str = String.format("姓名:%s,年龄:%d,成绩:%5.2f", name, age, score);
    	System.out.println(str); // 输出 姓名:皮卡丘,年龄:18,成绩:98.99
	}
}

课时57:其它操作方法

No方法名称类型描述
1public String concat(String str)普通连接字符串
2public String intern()普通将字符串手动入池
3public boolean isEmpty()普通判断字符串是否为空
4public int length()普通获取字符串的长度
5public String trim()普通去除字符串左右两边的空格
6public String toUpperCase()普通转大写
7public String toLowerCase()普通转小写
  • concat()方法:

    public class StringDemo {
        public static void main(String[] args) {
        	String strA = "pikaqiu";
        	String strB = "pi".concat("ka").concat("qiu");
        	System.out.println(strB); // 输出 pikaqiu
        	System.out.println(strA == strB); // 输出 false
    	}
    }
    

    (个人分析:从concat()方法的源代码可以看到,是通过数组拷贝实现的拼接,所以最后返回的是new String的新数组。new是开辟了新的堆空间。)

  • isEmpty()方法:

    public class StringDemo {
        public static void main(String[] args) {
        	String str = null;
            System.out.println("".isEmpty()); // 输出 true
            System.out.println("pikaqiu".isEmpty()); // 输出 false
            System.out.println(str.isEmpty()); // 抛出NullPointerException异常
    	}
    }
    

    在字符串定义时,“""”和“null”不是一个概念,前者表示有实例化对象,后者表示梅伊欧实例化对象,而isEmpty()是判断字符串的内容,所以应该在有实例化对象时进行调用,否则会抛出“java.lang.NullPointerException”的空指针异常。

  • length() 与 trim() 方法:

    public class StringDemo {
        public static void main(String[] args) {
        	String str = " hello pikaqiu ";
            System.out.println(str.length()); // 输出 15
            str = str.trim(); // 去首尾空格
            System.out.println(str.length()); // 输出 13
    	}
    }
    
  • toUpperCase() 与 toLowerCase() 方法:

    public class StringDemo {
        public static void main(String[] args) {
        	String str = "Hello Pikaqiu";
            System.out.println(str.toUpperCase()); // 输出 HELLO PIKAQIU
            System.out.println(str.toLowerCase()); // 输出 hello pikaqiu
            String str2 = "123456";
            System.out.println(str2.toUpperCase()); // 输出 123456
            System.out.println(str2.toLowerCase()); // 输出 123456
    	}
    }
    
  • 自定义首字母大写的方法:

    class StringUtil {
    	public static String initCap(String str) {
    		// null或空时直接返回
    		if (null == str || "".equals(str)) return str;
    		// 字符串长度为1时直接转大写并返回
            if (1 == str.length()) return str.toUpperCase();
            // 将首字母转大写后进行拼接再返回
            return str.substring(0, 1).toUpperCase() + str.substring(1);
    	}
    }
    public class StringDemo {
        public static void main(String[] args) {
            System.out.println(StringUtil.initCap("hello"));
    	}
    }
    

第12章:继承的定义与使用

课时58:继承问题引出

  • 面向对象的第二大特征就是继承性。继承性的主要特点在于:可以扩充已有类的功能。
  • 所谓的良好代码,指的是结构性合理、适合维护、可重用性很高。
  • 如果想要进行代码的重用,就必须使用继承的概念来解决,

课时59:继承的实现

  • 实现继承的关键字:extends。

  • 实现继承的语法:

    class 子类 extends 父类 {}
    

    特别需要注意的是,很多情况下会把子类称为派生类,把父类称为超类。

  • 子类实现继承:

class Person {
    private String name;
    private int age;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return this.age;
    }
    public String toString() {
        return "姓名:" + this.getName() + ",年龄:" + this.getAge();
    }
}
// Student是Person的子类,Person是Student的父类
class Student extends Person {
    // 在子类中不定义任何功能
}
public class JavaDemo {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("林大强"); // 父类定义
        stu.setAge(38); // 父类定义
        System.out.println(stu);
    }
}

继承操作.PNG

  • 继承实现的主要目的是在于子类可以重用父类中的结构,并且也可以实现功能的扩充,那么同时强调了:子类可以定义更多的内容,并且描述的范围更小。

  • 子类实现扩充:

class Student extends Person {
    private String school;
    public void setSchool(String school) {
        this.school = school;
    }
    public String getSchool() {
        return this.school;
    }
    public String toString() { // 重写父类方法
        return super.toString() + ",学校:" + this.getSchool();
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("林大强"); // 父类定义
        stu.setAge(38); // 父类定义
        stu.setSchool("家里蹲大学"); // 子类定义
        System.out.println(stu);
    }
}

子类扩充.PNG

课时60:子类对象实例化流程

  • 对于继承逻辑来说,在进行子类实例化对象的时候,一定要首先实例化父类对象。
class Person {
	public Person() {
		System.out.println("【Person父类】一个新的Person父类实例化对象产生了。");
	}
}
class Student extends Person {
	public Student() {
		System.out.println("【Student子类】一个新的Student子类实例化对象产生了。");
	}
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student(); // 实例化子类对象
//        执行结果如下:
//        【Person父类】一个新的Person父类实例化对象产生了。
//        【Student子类】一个新的Student子类实例化对象产生了。
    }
}
  • 如没有进行父类对象的实例化,也会由系统自动调用父类的构造方法(实例化父类对象)。
  • 默认情况下的子类实例化流程里,会自动实例化父类对象,相当于子类的构造方法里隐含了“super()”。
class Person {
	public Person() {
		System.out.println("【Person父类】一个新的Person父类实例化对象产生了。");
	}
}
class Student extends Person {
	public Student() {
		super(); // 写不写此语句都一样
		System.out.println("【Student子类】一个新的Student子类实例化对象产生了。");
	}
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student(); // 实例化子类对象
//        输出如下:
//        【Person父类】一个新的Person父类实例化对象产生了。
//        【Student子类】一个新的Student子类实例化对象产生了。
    }
}
  • “super()”表示的就是在子类构造方法中调用父类构造方法,该语句只允许放在子类构造方法的首行。
  • 在默认情况下,子类只会调用父类中无参的构造方法。
  • 所以请注意!如父类中没有提供无参构造,则必须在子类中使用super()来明确调用父类的有参构造。
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Person {

}
public class JavaDemo {
    public static void main(String[] args) {
        new Student(); // 实例化子类对象。此时执行,会抛出错误(非异常)。
    }
}
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Person {
    private String school;
    public Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student("林小强", 48, "北京大学"); // 实例化子类对象。正常实例化,无错误(非异常)抛出。
    }
}
  • super() 与 this() 都可以调用构造方法,super() 是由子类调用父类的构造,而 this() 是调用本类的构造,并且都一定要放在构造方法的首行,所以两个语句不允许同时出现。

课时61:继承定义限制

  1. Java之中不允许多重继承,只允许多层继承。
class A {}
class B {}
class C extends A,B {} // 多重继承
class A {}
class B extends A {}
class C extends B {} // 多层继承(最好不要超过三层)
  1. 子类可以继承父类中的所有操作结构,包括私有操作,但私有操作属于隐式继承。
class Person {
    public String name = "皮卡"; // name属性为public公共的
}
class Student extends Person {
    public Student() {
    	// 都正常输出 皮卡
        System.out.println("this.name = " + this.name);
        System.out.println("super.name = " + super.name);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student();
    }
}
class Person {
    private String name = "皮卡"; // name属性为private私有的
}
class Student extends Person {
    public Student() {
    	// 直接访问私有属性,会抛出错误(非异常)
        System.out.println("this.name = " + this.name);
        System.out.println("super.name = " + super.name);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student();
    }
}
class Person {
    private String name = "皮卡";
    public String getName() {
        return name;
    }
}
class Student extends Person {
    public Student() {
    	// 间接访问私有属性,正常调用
        System.out.println("this.getName() :" + this.getName());
        System.out.println("super.getName() :" + super.getName());
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        new Student();
    }
}

第13章:覆写

课时62:方法覆写

  • 方法的覆写(方法的重写):方法名称相同,参数类型及个数也完全相同(父类方法的访问控制权限不为private)。
class Channel {
    public void connect() {
        System.out.println("【Channel父类】进行资源的连接。");
    }
}
class DatabaseChannel extends Channel {
    public void connect() { // 进行方法的覆写
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseChannel channel = new DatabaseChannel();
        channel.connect(); // 输出 【DatabaseChannel子类】进行数据库资源的连接。
    }
}
  • 覆写的意义:优化父类的功能。

覆写意义.PNG

  • 在子类进行方法的覆写后,如果还想继续调用父类中的方法,那么就需要使用“super.方法名称()”来进行调用。
class Channel {
    public void connect() {
        System.out.println("【Channel父类】进行资源的连接。");
    }
}
class DatabaseChannel extends Channel {
    public void connect() { // 进行方法的覆写
    	super.connect(); // 调用父类的方法
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseChannel channel = new DatabaseChannel();
        channel.connect();
//        输出如下:
//        【Channel父类】进行资源的连接。
//        【DatabaseChannel子类】进行数据库资源的连接。
    }
}

课时63:方法覆写限制

  • 在子类中,覆写的方法不能拥有比父类方法更严格的访问控制权限。

  • 访问控制权限:public > default(不写) > private 。

class Channel {
    public void connect() { // 访问控制权限为public
        System.out.println("【Channel父类】进行资源的连接。");
    }
}
class DatabaseChannel extends Channel {
    void connect() { // 进行方法的覆写。访问控制权限为default,权限比public小,会抛出错误(非异常)
        super.connect();
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
class Channel {
    public void connect() { // 访问控制权限为public
        System.out.println("【Channel父类】进行资源的连接。");
    }
}
class DatabaseChannel extends Channel {
    void connect() { // 进行方法的覆写。访问控制权限为public,可以正常被调用
        super.connect();
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
  • 也就是说,子类中覆写方法的访问控制权限,只能大于或等于父类中被腹泻方法的访问控制权限。

  • 有一种特殊情况,是针对private权限的。在父类中定义private权限的属性或方法时,由于其是私有的,对于子类是不可见的,因此如果父类的方法是private的话,不存在子类覆写该方法。

class Channel {
    public void connect() { // 访问控制权限为public
        System.out.println("【Channel父类】进行资源的连接。");
    }
    public void fun() {
        this.connect();
    }
}
class DatabaseChannel extends Channel {
    public void connect() { // 进行方法的覆写
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseChannel channel = new DatabaseChannel();
        channel.fun(); // 输出 【DatabaseChannel子类】进行数据库资源的连接。
        // 调用父类中的fun()时,由于子类覆写了connect(),所以这里调用的是子类的connect()
    }
}
class Channel {
    private void connect() { // 访问控制权限为private
        System.out.println("【Channel父类】进行资源的连接。");
    }
    public void fun() {
        this.connect();
    }
}
class DatabaseChannel extends Channel {
    public void connect() { // 进行方法的覆写
        System.out.println("【DatabaseChannel子类】进行数据库资源的连接。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseChannel channel = new DatabaseChannel();
        channel.fun(); // 输出 【DatabaseChannel子类】进行数据库资源的连接。
        // 调用父类中的fun()时,由于父类中的connect()为private,不存在方法的覆写,所以这里调用的是父类的connect()
    }
}
// 总结说明
public void fun() {
	// 如存在方法覆写,则调用子类的connect()
	// 如不存在方法覆写,则调用父类的connect()
	this.connect();
}
个人总结:
	当存在继承关系,实例化的是子类对象时:
	1. 如调用了子类中未覆写的方法,则会往上查找,实际调用父类的方法。
	2. 如在父类方法中使用this关键字调用方法A,则会往下查找,判断子类中是否有对方法A进行覆写,有则调用子类方法,无则调用父类方法。
  • 面试题:请解释 Override 与 Overloading 的区别?
NO区别OverrideOverloading
1中文含义重载重写(覆写)
2概念方法名称相同,参数的类型及个数不同方法名称、参数类型及个数、返回值相同
3权限没有权限限制被重写方法不能拥有更严格的控制权限
4范围发生在一个类中发生在继承关系中
  • 在进行方法重载时,并没有对返回类型进行限制,但好的习惯应该是保持返回类型一致。

课时64:属性覆盖

  • 属性覆盖:在子类中定义了与父类相同名称的属性。
class Channel {
    String info = "pikaqiu";
}
class DatabaseChannel extends Channel {
    String info = "hello pikaqiu"; // 属性覆盖
    public void fun() {
        System.out.println("super.info = " + super.info); // 输出 super.info = pikaqiu
        System.out.println("this.info = " + this.info); // 输出 this.info = hello pikaqiu
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseChannel channel = new DatabaseChannel();
        channel.fun();
    }
}
  • 面试题:请解释 super 与 this 的区别?
    • this 表示先从本类开始查找属性或方法,如果本类没有,再去父类查找;super 表示直接在父类中查找。
    • this 和 super 都可以进行构造方法的调用,但 this() 是调用本类的构造方法;super() 是在子类中调用父类的构造方法。而由于 this() 和 super() 都必须放在构造方法首行,所以它们不能同时出现。
    • this 可以表示当前对象。

课时65:final关键字

  • final在程序之中描述的是一种终结器的概念。

  • 在java里,使用final关键字可以实现如下的功能:

    • 定义不能被继承的类;
    • 定义不能被覆写的方法 / 常量。
  • 使用 final 定义不能被继承的类:

final class Channel {} // 使用了final关键字定义类,则Channel类不可以被继承
class DatabaseChannel extends Channel {} // 想要继承Channel类,会抛出错误(非异常)
public class JavaDemo {
    public static void main(String[] args) {}
}
  • 使用 final 定义不能被继承的方法:
class Channel {
    public final void connect() {} // 使用了final关键字定义方法,则connect()方法不可以被覆写
}
class DatabaseChannel extends Channel {
    public void connect() {} // 想要覆写connect()方法,会抛出错误(非异常)
}
public class JavaDemo {
    public static void main(String[] args) {}
}
  • final关键字重要的应用:定义常量(不可被修改)
class Channel {
    private final int OFF = 0; // 使用了final关键字定义属性,则该属性不可被修改
    private final int ON = 1; // 使用了final关键字定义属性,则该属性不可被修改
    public final void connect() {
        ON = 2; // 想要修改ON属性,会抛出错误(非异常)
    }
}
public class JavaDemo {
    public static void main(String[] args) {}
}
  • 实际上常量往往都是公共的定义,所以为了可以体现出共享的概念,往往会使用“全局常量”的形式来定义:“public static final 属性名称”。
  • 在定义全局常量时,每个字母都必须用大写。
public class StringDemo {
    public static void main(String[] args) {
    	final String INFO = "ka"; // 使用final关键字定义属性,则info为常量
        String strA = "pikaqiu";
        String strB = "pi" + INFO + "qiu";
        System.out.println(strA == strB); // 输出 true
	}
}
// 可与课时45中的代码示例进行对比

第14章:综合案例:继承分析

课时66:案例分析一(学生类)

  • 建立一个人类(Person)和学生类(Student),功能要求如下:
    • Person:
      • 私有属性:
        • 姓名 name:字符串型
        • 地址 addr:字符串型
        • 性别 sex:字符型
        • 年龄 age:整型
      • 构造方法:四参、两参、无参
      • 输出方法:显示4种属性
    • Student(继承Person):
      • 属性:
        • 数学成绩 math
        • 英语成绩 english
      • 构造方法:六参、两参、无参
      • 输出方法:显示6种属性
class Person {
    private String name;
    private char sex;
    private int age;
    private String addr;
	public Person() {}
	public Person(String name, char sex) {
		this(name, sex, 0, "");
	}
    public Person(String name, char sex, int age, String addr) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.addr = addr;
    }
    public String getInfo() {
    	return "姓名:" + this.name +
    			",性别:" + this.sex +
    			",年龄:" + this.age +
    			",地址:" + this.addr;
    			
    }
}
class Student extends Person {
    private double math;
    private double english;
    public Student() {}
    public Student(double math, double english) {
        this.math = math;
        this.english = english;
    }
    public Student(String name, char sex, int age, String addr, double math, double english) {
        super(name, sex, age, addr);
        this.math = math;
        this.english = english;
    }
    public String getInfo() {
        return super.getInfo() + ",数学成绩:" + this.math + ",英语成绩:" + this.english;

    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Student stu = new Student("张三", '男', 18, "天安门", 78.99, 89.98);
        System.out.println(stu.getInfo());
    }
}

课时67:案例分析二(管理人员与职员)

  • 定义员工类:
    • 属性:姓名、年龄、性别
    • 构造方法
    • 输出方法
  • 定义管理层类(继承员工类):
    • 属性:职务、年薪
  • 定义职员类(继承员工类):
    • 属性:所属部门、月薪
class Employee {
    private String name;
    private int age;
    private char sex;
    public Employee() {}
    public Employee(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public String getInfo() {
        return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.sex;
    }
}
class Manager extends Employee {
    private String job;
    private double income;
    public Manager() {}
    public Manager(String name, int age, char sex, String job, double income) {
        super(name, age, sex);
        this.job = job;
        this.income = income;
    }
    public String getInfo() {
        return "【管理层】" + super.getInfo() + ",职务:" + this.job + ",年薪:" + this.income;
    }
}
class Staff extends Employee {
    private String dept;
    private double salary;
    public Staff() {}
    public Staff(String name, int age, char sex, String dept, double salary) {
        super(name, age, sex);
        this.dept = dept;
        this.salary = salary;
    }
    public String getInfo() {
        return "【职员】" + super.getInfo() + ",部门:" + this.dept + ",月薪:" + this.salary;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Manager man = new Manager("张三", 28, '女', "主管", 150000.00);
        Staff sta = new Staff("李四", 18, '男', "出纳", 3000.00);
        System.out.println(man.getInfo());
        System.out.println(sta.getInfo());
    }
}

课时68:案例分析三(字符串统计)

  • 编写程序,统计出字符串“want you to know one thing”中字母n和字母o出现的次数。
  • 方案一(顺序式思维模式):
class StringUtil {
	// 返回的整型数组,第一个内容为字母n的个数,第二个内容为字母o的个数
	public static int[] count(String str) {
		int[] countData = new int[2];
		char[] data = str.toCharArray(); // 将字符串转为字符数组
		for (int i = 0; i < data.length; i ++) {
			switch (data[i]) {
				case 'n':
				case 'N':
					countData[0] ++;
					break;
				case 'o':
				case 'O':
					countData[1] ++;
					break;
			}
		}
		return countData;
	}
}
public class JavaDemo {
    public static void main(String[] args) {
    	String str = "want you to know one thing";
    	int[] result = StringUtil.count(str);
        System.out.println("字母n的个数:" + result[0]);
        System.out.println("字母o的个数:" + result[1]);
    }
}
  • 方案二(结构化设计):
class StringUtil {
	private String content;
	public StringUtil(String content) {
		this.content = content;
	}
	public String getContent() {
		return this.content;
	}
}
class StringCount extends StringUtil {
	private int nCount;
	private int oCount;
	public StringCount(String content) {
		super(content);
		this.countChar();
	}
	public void countChar() {
		char[] data = super.getContent().toCharArray(); // 将字符串转为字符数组
		for (int i = 0; i < data.length; i ++) {
			switch (data[i]) {
				case 'n':
				case 'N':
					nCount ++;
					break;
				case 'o':
				case 'O':
					oCount ++;
					break;
			}
		}
	}
	public int getNCount() {
		return nCount;
	}
	public int getOCount() {
		return oCount;
	}
	public String getInfo() {
		return "字母n的个数:" + this.nCount + ",字母o的个数:" + this.oCount;
	}
}
public class JavaDemo {
    public static void main(String[] args) {
    	StringCount sc = new StringCount("want you to know one thing");
        System.out.println(sc.getInfo());
    }
}

课时69:案例分析四(数组分析)

  • 可以实现整型数组的操作类(Array)

    • 数组的大小由外部决定。
    • 可以进行数据的增加(如果满了则无法增加);
    • 可以实现数组的容量扩充;
    • 可以获取数组的全部内容。
  • 操作类(Array)的子类:

    • 数组排序类:返回排序后的数组
    • 数组反转类:返回首尾交换后的数组
  • 实现步骤:

    • 第一步:实现基本的数组操作类的定义:

      class Array { // 数组的操作类
          private int foot; // 进行数组的脚标控制
          private int[] data; // 整型数组
          public Array(int len) {
              if (0 < len) this.data = new int[len]; // 开辟指定大小的数组空间
              else this.data = new int[1]; // 默认开辟一个长度的数组空间
          }
          public int[] getData() {
              return this.data;
          }
          // 增加数据
          public boolean add(int num) {
              if (this.data.length > this.foot) { // 有空位置
                  this.data[this.foot ++] = num;
                  return true;
              }
              return false;
          }
          // 扩充数组(扩充num个长度)
          public void increment(int num) {
              int[] newData = new int [this.data.length + num]; // 创建新数组
              System.arraycopy(this.data, 0, newData, 0, this.data.length); // 拷贝数组
              this.data = newData; // 赋值新数组给data
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              Array arr = new Array(5);
              System.out.println(arr.add(10));
              System.out.println(arr.add(5));
              System.out.println(arr.add(20));
              System.out.println(arr.add(3));
              System.out.println(arr.add(6));
              arr.increment(3);
              System.out.println(arr.add(1));
              System.out.println(arr.add(7));
              System.out.println(arr.add(0));
          }
      }
      
    • 第二步:实现排序子类的定义:

      class SortArray extends Array { // 数组操作的排序子类
          public SortArray(int len) {
              super(len);
          }
          public int[] getData() {
              java.util.Arrays.sort(super.getData()); // 排序
              return super.getData();
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              SortArray arr = new SortArray(5);
              arr.add(10);
              arr.add(5);
              arr.add(20);
              arr.add(3);
              arr.add(6);
              int[] result = arr.getData();
              for (int temp : result) {
                  System.out.print(temp + " ");
              }
              // 最终输出:3 5 6 10 20 
          }
      }
      
    • 第三步:实现反转子类的定义:

      class ReverseArray extends Array { // 数组操作的反转子类
          public ReverseArray(int len) {
              super(len);
          }
          public int[] getData() {
              int center = super.getData().length / 2;
              int head = 0;
              int tail = super.getData().length - 1;
              for (int i = 0; i < center; i++) {
                  int temp = super.getData()[head];
                  super.getData()[head] = super.getData()[tail];
                  super.getData()[tail] = temp;
                  head ++;
                  tail --;
              }
              return super.getData();
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              ReverseArray arr = new ReverseArray(5);
              arr.add(10);
              arr.add(5);
              arr.add(20);
              arr.add(3);
              arr.add(6);
              int[] result = arr.getData();
              for (int temp : result) {
                  System.out.print(temp + " ");
              }
              // 最终输出:6 3 20 5 10 
          }
      }
      

第15章:Annotation注解

课时70:Annotation简介

  • Annotation时从JDK1.5之后提出的一个新的开发技术结构,利用Annotation可以有效地减少程序配置的代码,并且可以进行一些结构化的定义。

  • Annotation是以一种注解的形式实现的程序开发。

  • 程序开发结构的历史:

    • 过程一:

      • 在程序定义的时候,将所有可能使用到的资源全部定义在程序代码之中。
      • 缺点:不方便修改。如一个资源改变,可能要修改多处地方。

      程序开发过程一.PNG

    • 过程二:

      • 引入配置文件,在配置文件之中定义全部要使用的服务器资源。
      • 缺点:配置文件可能会很多。

      程序开发过程二.PNG

    • 过程三:

      • 将配置信息重新写回到程序里面,利用一些特殊的标记与程序代码进行分离,这就是注解的作用,也就是Annotation提出的基本依据。
      • 但如果全部使用注解开发 ,难度太高,因此现在开发基本上是采用 配置文件 + 注解 的形式。
  • Java提供的基本注解:

    • @Override
    • @Deprecated
    • @SuppressWarnings

课时71:准确覆写

  • 准确覆写:@Override

  • 当子类继承一个父类之后,如果发现父类中的某些方法功能不足,往往会采用覆写的形式来对方法功能进行扩充。

  • 实现继承时经常出现的两个问题:

    • 问题一:在定义子类时,忘记extends父类,那这样就不是覆写了。

      class Channel {
          public void connect() {
              System.out.println("———————————— 通道的连接 ————————————");
          }
      }
      class DatabaseChannel {
          public void connect() {
              System.out.println("———————————— 数据库通道的连接 ————————————");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              new DatabaseChannel().connect();
              // 输出:———————————— 数据库通道的连接 ————————————
          }
      }
      
    • 问题二:在子类中覆写方法时,方法名称写错了。

      class Channel {
          public void connect() {
              System.out.println("———————————— 通道的连接 ————————————");
          }
      }
      class DatabaseChannel extends Channel {
          public void connection() {
              System.out.println("———————————— 数据库通道的连接 ————————————");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              new DatabaseChannel().connect();
              // 输出:———————————— 通道的连接 ————————————
          }
      }
      
    • 所以为了避免上述两个问题,则可以在明确覆写的方法上增加“@Override”注解,这样在编译时才会产生错误信息。

      class Channel {
          public void connect() {
              System.out.println("———————————— 通道的连接 ————————————");
          }
      }
      class DatabaseChannel extends Channel {
      	@Override
          public void connection() {
              System.out.println("———————————— 数据库通道的连接 ————————————");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              new DatabaseChannel().connect(); // 抛出错误(非异常)信息
          }
      }
      
  • 该注解主要是帮助开发者在程序编译时就可以检查出程序的错误。

课时72:过期声明

  • 过期操作:@Deprecated

  • 过期操作:指的是在一个软件项目的迭代开发过程之中,可能有某个方法或类,由于在最初设计时考虑不周(存在缺陷),导致新版本的应用会有不适应的地方(老版本不受影响),这时候又不可能直接删除掉这些操作,那么就给一个过度的时间,此时就可以采用过期的声明。目的在于告诉新的用户,这些操作不要再用了。

class Channel {
	@Deprecated // 老系统可以继续用,新系统就不要再用了
    public void connect() {
        System.out.println("———————————— 通道的连接 ————————————");
    }
    public String connection() {
    	return "获取了通道连接信息。";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        new Channel().connect(); // 编译时出现错误提示信息,而运行时可以正常调用
        // 输出:———————————— 通道的连接 ————————————
    }
}

课时73:压制警告

  • 压制警告:@SuppressWarings

  • 如不想在编译时出现错误的提示信息(如由于使用@Deprecated方法而导致的“使用或覆盖了已过时的API。”),或已经明确知道错误在哪里,那么就可以进行警告信息的压制。

class Channel {
    @Deprecated // 老系统可以继续用,新系统就不要再用了
    public void connect() {
        System.out.println("———————————— 通道的连接 ————————————");
    }
    public String connection() {
        return "获取了通道连接信息。";
    }
}
public class JavaDemo {
    @SuppressWarnings("all") // 参数为警告类型,“all”表示压制所有警告
    public static void main(String[] args) {
        new Channel().connect(); // 编译时不再出现错误提示信息
        // 输出:———————————— 通道的连接 ————————————
    }
}

第16章:多态性

课时74:多态性简介

  • 多态性:面向对象的第三大主要特征,是在继承性的基础上扩展出来的概念,可以实现父子类之间的转换处理。

  • Java中实现多态性的模式:

    • 方法的多态性:

      • 方法的重载:同一个方法名称,可以根据传入参数的类型或个数的不同,来实现不同的功能;

        class Message {
        	public void print() {
        		System.out.println("pikaqiu");
        	}
        	public void print(String str) {
        		System.out.println(str);
        	}
        }
        
      • 方法的重写(覆写):

        class Message {
        	public void print() {
        		System.out.println("pikaqiu");
        	}
        }
        class DatabaseMessage extends Message {
        	public void print() {
        		System.out.println("pikaqiu's database");
        	}
        }
        
    • 对象的多态性:父子实例间的转换处理。

      • 对象向上转型(自动转型,90%):父类 父类实例= 子类实例;
      • 对象向下转型(强制转型,3%,需要使用子类的特殊功能时):子类 子类实例 = (子类) 父类实例;
      • (不需要转型,7%,如String类)

课时75:对象向上转型

  • 向上转型的主要特点:可以对参数进行统一的设计。
class Message {
    public void print() {
        System.out.println("pikaqiu");
    }
}
class DatabaseMessage extends Message {
    public void print() {
        System.out.println("pikaqiu's database");
    }
}
class ServerMessage extends Message {
    public void print() {
        System.out.println("pikaqiu's server");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        fun(new DatabaseMessage()); // Message message = new DatabaseMessage();
        fun(new ServerMessage()); // Message message = new ServerMessage();
    }
    public static void fun(Message message) {
        message.print();
    }
}
  • 对象向上转型:实现接收或返回参数的统一性。

课时76:对象向下转型

  • 向下转型的主要特点:需要使用子类的特殊定义处理。

对象向下转型.PNG

class Person {
	public void print() {
        System.out.println("一个正常的人类行为:吃饭、睡觉。");
    }
}
class SuperMan extends Person {
	public void fly() {
		System.out.println("我可以飞~");
	}
	public void fire() {
		System.out.println("我可以喷火~");
	}
}
public class JavaDemo {
    public static void main(String[] args) {
    	System.out.println("———————————— 正常状态下的超人应该是一个普通人 ————————————");
    	Person per = new SuperMan(); // 向上转型
    	per.print();
    	System.out.println("———————————— 外星怪兽狗骚扰地球,准备消灭人类 ————————————");
    	SuperMan man = (SuperMan) per;
    	man.fly();
    	man.fire();
    }
}
  • 发生向下转型前一定要首先发生向上转型,否则会抛出“ClassCastException”的异常。
public class JavaDemo {
    public static void main(String[] args) {
        Person per = new Person();
        SuperMan man = (SuperMan) per;
        man.fly();
        man.fire();
}

课时77:instanceof关键字

  • 由于向下转型是一个存在有安全隐患的操作,因此为了保证向下转型的正确性,往往需要在向下转型之前进行判断,判断某个实例是否为某个类的对象,这就需要通过instanceof关键字来实现。

  • instanceof关键字的使用语法:对象 instanceof 类

  • instanceof判断的返回值类型:boolean型。

public class JavaDemo {
    public static void main(String[] args) {
        Person per1 = new Person(); // 不转型
        System.out.println(per1 instanceof Person); // 输出:true
        System.out.println(per1 instanceof SuperMan); // 输出:false
        System.out.println("—————— 分界线 ——————");
        Person per2 = new SuperMan(); // 向上转型
        System.out.println(per2 instanceof Person); // 输出:true
        System.out.println(per2 instanceof SuperMan); // 输出:true
    }
}
  • 在进行向下转型前,应该先使用instanceof进行判断
public class JavaDemo {
    public static void main(String[] args) {
        Person per = new Person();
        if (per instanceof SuperMan) {
            SuperMan man = (SuperMan) per;
            man.fly();
            man.fire();
        }
    }
}

第17章:Object类

课时78:Object类基本概念

  • Object类的主要特点:可以解决参数的统一问题。(Object类可以接收所有的数据类型。)

  • 在Java中,只有一个类是不存在父类的,这个类就是Object类。

  • 所有的类在默认情况下,都是Object的子类,所以可以使用Object来接收所有的子类。

class Person {}
public class JavaDemo {
	public static void main(String[] args) {
		Object obj = new Person(); // 向上转型
		if (obj instanceof Person) {
			Person per = (Person) obj;
			System.out.println("Person对象向下转型执行完毕。");
		}
	}
}

课时79:取得对象信息

  • 获取对象信息:toString()

  • 在Object类中提供有 “toString()” 的方法,该方法可以获取一个对象的完整信息:public String toString()

class Person {}
public class JavaDemo {
    public static void main(String[] args) {
        Person per = new Person();
        System.out.println(per);
        System.out.println(per.toString());
    }
}
  • 从上述代码可以发现,在对象直接输出时,实际调用的就是toString()方法,所以这个方法调用与不调用都是一样的。因此在对象信息获取时,可以直接覆写此方法。
class Person {
	@Override
    public String toString() {
        return "我是一个人";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Person per = new Person();
        System.out.println(per); // 输出:我是一个人
    }
}

课时80:对象比较

  • 对象比较:equals()

  • 对象比较的主要功能:比较两个对象的内容是否完全相同。

  • 在Object类中提供有 “equals()” 的方法:public boolean equals(Object obj) ,默认情况下该方法只进行了两个对象的地址判断。

    public boolean equals(Object obj) {
        return (this == obj);
    }
    
  • 案例:

    • 传统比较:

      class Person {
          private String name;
          private int age;
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
          public String getName() {
              return name;
          }
          public int getAge() {
              return age;
          }
          @Override
          public String toString() {
              return "姓名:" + this.name + ",年龄:" + this.age;
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              Person perA = new Person("张三", 18);
              Person perB = new Person("张三", 18);
              if (perA.getName().equals(perB.getName())
                      && perA.getAge() == perB.getAge()) {
                  System.out.println("是同一个人");
              }
              else System.out.println("不是同一个人");
          }
      }
      

      (对象比较应该是类内部具备的功能,不应该在类外部进行定义。)

    • 覆写equals()方法进行比较:

      class Person {
          private String name;
          private int age;
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
          public String getName() {
              return name;
          }
          public int getAge() {
              return age;
          }
          @Override
          public boolean equals(Object obj) {
              if (null != obj) return false; // 如果为null,则不需要进行比较
              if (this == obj) return true; // 如果内存地址相同,则为同一个对象
              if (!(obj instanceof Person)) return false; // 判断是否可以向下转型
              Person per = (Person) obj;
              return this.getName() == per.getName() && this.getAge() == per.getAge();
          }
          @Override
          public String toString() {
              return "姓名:" + this.name + ",年龄:" + this.age;
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              Person perA = new Person("张三", 18);
              Person perB = new Person("张三", 18);
              System.out.println(perA.equals(perB));
          }
      }
      
  • String类作为Object的子类,实际上已经覆写了equals()方法

第18章:抽象类的定义与使用

课时81:抽象类基本概念

  • 在类的继承操作中,对于子类是否需要覆写某个方法,父类无法做出强制性约定。而实际开发中,很少会出现继承一个已经很完善的类。

  • 在以后进行父类设计时,应该优先考虑抽象类。

  • 抽象类的主要作用:可以约定必须在子类中需要进行覆写的方法,也就是可以在抽象类里定义抽象方法。

  • 抽象方法:使用abstract关键字定义的,并且不提供方法体的方法。其所在的类必须为抽象类。

abstract class Message { // 抽象类
    private String type; // 消息类型
    public abstract String getConnectInfo(); // 抽象方法
    public String getType() { // 普通方法
        return type;
    }
    public void setType(String type) { // 普通方法
        this.type = type;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Message msg = new Message(); // 会抛出错误(非异常),抽象类无法实例化
        System.out.println(msg.getConnectInfo());
    }
}
  • 切记:“抽象类不是完整的类”。
  • 使用抽象类的原则:
    • 抽象类必须提供有子类,子类使用extends继承一个抽象类;
    • 抽象类的子类(非抽象类)必须覆写抽象类中的全部抽象方法;
    • 抽象类的对象实例化,可以利用对象多态性(通过子类进行向上转型)来完成。
abstract class Message { // 抽象类
    private String type; // 消息类型
    public abstract String getConnectInfo(); // 抽象方法
    public String getType() { // 普通方法
        return type;
    }
    public void setType(String type) { // 普通方法
        this.type = type;
    }
}
class DatabaseMessage extends Message {} // 会抛出错误(非异常),继承了抽象类,但没有覆写抽象类中的抽象方法
public class JavaDemo {
    public static void main(String[] args) {}
}
abstract class Message { // 抽象类
    private String type; // 消息类型
    public abstract String getConnectInfo(); // 抽象方法
    public String getType() { // 普通方法
        return type;
    }
    public void setType(String type) { // 普通方法
        this.type = type;
    }
}
class DatabaseMessage extends Message {
    @Override
    public String getConnectInfo() {
        return "我是数据库消息类";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        DatabaseMessage msg = new DatabaseMessage();
        System.out.println(msg.getConnectInfo()); // 调用覆写的抽象方法,正常输出
        msg.setType("数据库消息");
        System.out.println(msg.getType()); // 调用普通方法,正常输出
    }
}
  • 抽象类只是比普通类多了抽象方法以及对子类强制性覆写的要求,其它完全相同。

  • 使用抽象类的意见及建议:

    • 抽象类的核心问题:无法直接实例化;
    • 抽象类的主要目的:进行过渡操作的使用,往往是在设计中需要解决类继承关系中带来的代码重复问题。

课时82:抽象类的相关说明

  • 使用抽象类的注意事项:

    • 在定义抽象类时,不可以使用final关键字,因为抽象类必须要有子类,而final定义的类是不能够有子类的。

    • 抽象类中允许没有抽象方法。

    • 抽象类中可以提供static方法,并且该方法不受抽象类对象的局限。

      abstract class Message {
          public static Message getInstance() {
              return new DatabaseMessage();
          }
          public abstract String getInfo();
      }
      class DatabaseMessage extends Message {
          @Override
          public String getInfo() {
              return "数据库连接信息";
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              Message msg = Message.getInstance();
              System.out.println(msg.getInfo());
          }
      }
      

      ▲ static方法永远不受实例化对象或结构䣌限制,永远可以直接通过类名称进行调用。

课时83:模板设计模式

abstract class Action {
    public static final int EAT = 1;
    public static final int SLEEP = 5;
    public static final int WORK = 10;
    public void command(int code) {
        switch (code) {
            case EAT:
                this.eat();
                break;
            case SLEEP:
                this.sleep();
                break;
            case WORK:
                this.work();
                break;
        }
    }
    public abstract void eat();
    public abstract void sleep();
    public abstract void work();
}
class Robot extends Action {
    @Override
    public void eat() {
        System.out.println("机器人需要接通电源充电。");
    }
    @Override
    public void sleep() {}
    @Override
    public void work() {
        System.out.println("机器人按照固定的套路进行工作。");
    }
}
class Person extends Action {
    @Override
    public void eat() {
        System.out.println("饿的时候坐下,安静地吃饭。");
    }
    @Override
    public void sleep() {
        System.out.println("安静地躺下,慢慢地睡着。");
    }
    @Override
    public void work() {
        System.out.println("人类是高级动物,所以要有想法地工作。");
    }
}
class Pig extends Action {
    @Override
    public void eat() {
        System.out.println("吃食槽中人类的剩饭。");
    }
    @Override
    public void sleep() {
        System.out.println("倒地就睡。");
    }
    @Override
    public void work() {}
}
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("—————— 机器人行为 ——————");
        Action robotAction = new Robot();
        robotAction.command(Action.EAT);
        robotAction.command(Action.SLEEP);
        robotAction.command(Action.WORK);
        System.out.println("—————— 人类行为 ——————");
        Action personAction = new Person();
        personAction.command(Action.EAT);
        personAction.command(Action.SLEEP);
        personAction.command(Action.WORK);
        System.out.println("—————— 猪的行为 ——————");
        Action pigAction = new Pig();
        pigAction.command(Action.EAT);
        pigAction.command(Action.SLEEP);
        pigAction.command(Action.WORK);
    }
}
  • 抽象类的目的:对所有行为规范进行统一处理(提供模板)。

  • 抽象类最大的好处:

    • 实现对子类方法的统一管理
    • 可以提供普通方法,并且这些普通方法可以调用抽象方法(这些抽象方法在子类提供实现时才会生效)。

第19章:包装类

课时84:包装类实现原理分析

  • 包装类的主要功能是针对基本数据类型的对象转换而实现的,并且随着JDK版本的更新,包装类的功能也在发生着改变,有着更多的支持。

  • Object类最大的特点是所有类的父类,可以接收所有的数据类型,但基本数据类型都不是类,所以如果想要将基本数据类型以类的形式进行处理的话,就需要对其进行包装。

class Int {
    private int data;
    public Int(int data) {
        this.data = data;
    }
    public int intValue() {
        return this.data;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Int temp = new Int(10); // 装箱:将基本数据类型保存在包装类对象中
        int x = temp.intValue(); // 拆箱:从包装类对象中获取基本数据类型
        System.out.println(x);
    }
}
  • 基本数据类型进行包装处理后,可以像对象一样进行引用传递,同时也可以进行Object类来进行接收。

包装类.PNG

  • 包装类的类型:

    • 对象型包装类(Object类的直接子类):Boolean、Character
    • 数值型包装类(Number类的直接子类):Byte、Short、Integer、Long、Float、Double
  • Numer类中提供有直接获取包装类中基本数据类型的方法,一共只定义了六个方法:

No方法名称类型描述
1public byte byteValue()普通从包装类中获取byte数据
2public short shortValue()普通从包装类中获取short数据
3public abstract int intValue()普通从包装类中获取int数据
4public abstract long longValue()普通从包装类中获取long数据
5public abstract float floatValue()普通从包装类中获取float数据
6public abstract double doubleValue()普通从包装类中获取double数据

课时85:装箱与拆箱

  • 基本数据类型的包装类,都是为了基本数据类型转为对象而提供的。

  • 基本数据类型与包装类的操作:

    • 数据装箱:将基本数据类型保存到包装类之中,一般可以利用构造方法来完成。
      • Integer类:public Integer(int value);
      • Double类:public Double(double value);
      • Boolean类:public Boolean(boolean value);
    • 数据拆箱:从包装类中获取基本数据类型。
      • 数值型包装类:已由Number类定义了拆箱的方法。
      • Boolean型:public boolean booleanValue();
public class JavaDemo {
    public static void main(String[] args) {
    	Integer obj = new Integer(10); // 装箱
    	int num = obj.intValue(); // 拆箱
    	System.out.println(num);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Double obj = new Double(10.1); // 装箱
        double num = obj.doubleValue(); // 拆箱
        System.out.println(num);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Boolean obj = new Boolean(true); // 装箱
        boolean num = obj.booleanValue(); // 拆箱
        System.out.println(num);
    }
}
  • 从JDK1.9之后,对于所有包装类提供的构造方法都变为了过期处理,不建议用户再继续使用,这是因为在JDK1.5之后,为了方便处理,提供了自动的装箱与拆箱的操作。
public class JavaDemo {
    public static void main(String[] args) {
        Integer obj = 10; // 自动装箱
        int num = obj; // 自动拆箱
        obj ++; // 包装类对象可以直接参与数学运算
        System.out.println(num); // 输出:10
        System.out.println(obj); // 输出:11
    }
}
  • 除了提供有自动的数据运算支持外,使用自动装箱最大的好处是可以实现Object接收基本数据类型的操作。
public class JavaDemo {
    public static void main(String[] args) {
    	Object obj = 19.2; // double自动装箱为包装类,向上转型为Object
    	double num = (Double) obj; // 向下转型为包装类,再自动拆箱
        System.out.println(num); // 输出:19.2
    }
}
  • 相等问题:
public class JavaDemo {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        System.out.println(x == y); // 输出:true
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y); // 输出:false
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Integer x = -128;
        Integer y = -128;
        System.out.println(x == y); // 输出:true
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Integer x = -129;
        Integer y = -129;
        System.out.println(x == y); // 输出:false
    }
}

-128~127为byte的范围,超过byte就不止占1位了。

public class JavaDemo {
    public static void main(String[] args) {
        Integer x = -129;
        Integer y = -129;
        System.out.println(x == y); // 输出:false
        System.out.println(x.equals(y)); // 输出:true
    }
}
  • 包装类本身也需要考虑占位的长度,如果超过了1位的内容,那么就需要使用equals()来进行比较,否则使用“==”即可判断。
public class JavaDemo {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = new Integer(127); // 使用了new关键字,开辟新的堆空间
        System.out.println(x == y); // 输出:false
        System.out.println(x.equals(y)); // 输出:true
    }
}

第20章:接口的定义与使用

课时86:接口基本定义

  • 当你可以灵活地使用抽象类和接口进行设计时,那么基本上就表示你对面向对象的概念理解了。这一步是需要大量的程序代码累积而成的。

  • 接口的基本定义:

    • 抽象类与普通类相比最大的优势在于,可以实现对子类覆写方法的控制,但是在抽象类里面可能依然会保留有一些普通方法,而普通方法里面可能会设计到一些安全或隐私的操作问题,那么这样在开发过程中,如果想要对外部隐藏全部的实现细节,则可以通过接口来进行描述。
    • 接口可以理解为一个纯粹的抽象类(最原始的定义接口之中是只包含有抽象方法与全局常量的),但是从JDK1.8开始由于引用了Lambda表达式的概念,所以接口的定义也得到了加强:除了抽象方法与全局常量外,还可以定义普通方法或静态方法。但从设计本身的角度来讲,接口还是应该以抽象方法和全局常量为主。
    • 在java中,使用interface关键字来定义接口。
// 由于类名称与接口名称的定义要求相同,所以为了区分出接口,接口名称前往往会加字母I
interface IMessage { // 定义了一个接口
	public static final String INFO = "pikaqiu"; // 全局常量
	public abstract String getInfo(); // 抽象方法
}
  • 接口的使用原则:
    • 接口需要被子类实现(implements),一个子类可以实现多个父接口;
    • 子类(非抽象类)一定要覆写接口中的全部抽象方法;
    • 接口对象可以利用子类对象的向上转型进行实例化。
// 由于类名称与接口名称的定义要求相同,所以为了区分出接口,接口名称前往往会加字母I
interface IMessage { // 定义了一个接口
    public static final String INFO = "pikaqiu"; // 全局常量
    public abstract String getInfo(); // 抽象方法
}
class MessageImpl implements IMessage { // 实现了接口
    @Override
    public String getInfo() {
        return "得到一个消息";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl();
        System.out.println(msg.getInfo()); // 输出覆写方法
        System.out.println(msg.INFO); // 输出全局常量
    }
}
  • 一个子类可以实现多个接口
// 由于类名称与接口名称的定义要求相同,所以为了区分出接口,接口名称前往往会加字母I
interface IMessage { // 定义了一个接口
    public static final String INFO = "pikaqiu"; // 全局常量
    public abstract String getInfo(); // 抽象方法
}
interface IChannel {
    public abstract boolean connect();  // 抽象方法
}
class MessageImpl implements IMessage,IChannel { // 实现了接口
    @Override
    public String getInfo() {
        if (this.connect()) return "得到一个消息。";
        else return "通道创建失败,无法获取消息。";
    }
    @Override
    public boolean connect() {
        System.out.println("消息发送通道已经成功建立。");
        return true;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl();
        System.out.println(msg.getInfo());
        System.out.println(msg.INFO);
    }
}
  • 由于接口描述的是一个公共的定义标准,所以在接口中所有抽象方法的访问权限都为public,也就是说写不写public都是一样的。

    • 完整定义:

      interface IMessage { // 定义了一个接口
          public static final String INFO = "pikaqiu"; // 全局常量
          public abstract String getInfo(); // 抽象方法
      }
      
    • 简化定义:

      interface IMessage { // 定义了一个接口
          String INFO = "pikaqiu"; // 全局常量
          String getInfo(); // 抽象方法
      }
      
  • 一个类可以实现多个接口,但只能够继承一个类,要求先继承后实现。

  • 接口多继承:一个接口可以通过extends关键字继承若干个父接口,

interface IMessage {
	public abstract String getInfo();
}
interface IChannel {
	public boolean connect();
}
// extends在类继承上只能够继承一个父类,但接口上可以继承多个
interface IService extends IMessage,IChannel { // 接口多继承
	public String service();
}
class MessageService implements IService {
	public String getInfo() {}
	public boolean connect() {
		return true;
	}
	public String service() {
		return "获取消息的服务";
	}
}
  • 在实际开发中,接口使用的形式:
    • 进行标准的设置;
    • 表示一种操作的能力;
    • 暴露远程方法的视图(一般在RPC分布式开发中使用)。

课时87:接口定义加强

接口不当设计.PNG

  • 由于可能存在接口结构设计不当的问题,所以为了方便子类的修改,往往不会让子类直接实现接口,而是在中间追加一个过渡的抽象类。

接口不当设计的修改方案.PNG

  • 从JDK1.8后,为了解决接口设计的缺陷,所以允许在接口中定义普通方法。
interface IMessage {
    String getInfo();
    default boolean connect() { // 公共方法
        System.out.println("建立消息的发送通道。");
        return true;
    }
}
class MessageImpl implements IMessage {
    @Override
    public String getInfo() {
        return "pikaqiu";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl();
        if (msg.connect()) msg.getInfo();
    }
}
  • 接口中的普通方法必须使用default的声明,并且需要注意的是,该操作属于挽救措施,所以在非必须的情况下,不应该作为设计的首选。

  • 在接口中定义静态方法:

interface IMessage {
    String getInfo();
    default boolean connect() { // 公共方法
        System.out.println("建立消息的发送通道。");
        return true;
    }
    static IMessage getInstance() { // 静态方法
        return new MessageImpl(); // 获得子类对象
    }
}
class MessageImpl implements IMessage {
    @Override
    public String getInfo() {
        return "pikaqiu";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = IMessage.getInstance(); // 调用接口的静态方法
        if (msg.connect()) msg.getInfo();
    }
}
  • 由于可以在接口里定义普通方法和静态方法,这样接口在功能上来说,就可以取代抽象类了,但这两个组成不可以作为接口的主要设计原则。
  • 使用接口需要奉行的原则:接口就是抽象方法。

课时88:使用接口定义标准

接口应用.PNG

interface IUSB { // 定义USB标准
    boolean check();
    void work();
}
class Computer {
    public void plugin(IUSB usb) {
        if (usb.check()) usb.work();
    }
}
class Keyboard implements IUSB {
    @Override
    public boolean check() {
        return true;
    }
    @Override
    public void work() {
        System.out.println("开始进行码字任务。");
    }
}
class Print implements IUSB {
    @Override
    public boolean check() {
        return false;
    }
    @Override
    public void work() {
        System.out.println("开始进行照片打印。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.plugin(new Keyboard()); // 插入键盘
        computer.plugin(new Print()); // 插入打印机
    }
}

课时89:工厂设计模式

  • 对象实例化中存在的设计问题:

    • 想吃面包:

      interface IFood { // 定义一个食物标准
          void eat(); // 吃
      }
      class Bread implements IFood { // 定义面包类
          @Override
          public void eat() {
              System.out.println("吃面包。");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IFood food = new Bread();
              food.eat(); // 吃面包
          }
      }
      

      程序结构设计.PNG

    • 不想吃面包了,想喝牛奶:

      interface IFood { // 定义一个食物标准
          void eat(); // 吃
      }
      class Bread implements IFood { // 定义面包类
          @Override
          public void eat() {
              System.out.println("吃面包。");
          }
      }
      class Milk implements IFood { // 定义面包类
          @Override
          public void eat() {
              System.out.println("喝牛奶。");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IFood food = new Milk();
              food.eat(); // 喝牛奶
          }
      }
      
    • 上述两个程序表出现了耦合的问题,而造成耦合最直接的元凶:new关键字。

    • 而以JVM的设计为例,Java能实现可移植性的关键在于JVM,而JVM的核心原理:利用一个虚拟机来运行Java程序,所有的程序并不与具体的操作系统有任何的关联,而是由JVM来进行匹配。

    • 所以得出结论:良好的设计应该避免耦合。

  • 工厂设计模式:

    • 改进代码:

      import java.util.Scanner;
      interface IFood { // 定义一个食物标准
          void eat(); // 吃
      }
      class Bread implements IFood { // 定义面包类
          @Override
          public void eat() {
              System.out.println("吃面包。");
          }
      }
      class Milk implements IFood { // 定义面包类
          @Override
          public void eat() {
              System.out.println("喝牛奶。");
          }
      }
      class Factory {
          public static IFood getInstance(String className) {
              if ("bread".equals(className)) {
                  return new Bread();
              }
              else if ("milk".equals(className)) {
                  return new Bread();
              }
              else return null;
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IFood food = Factory.getInstance(args[0]);
              food.eat();
          }
      }
      

      工厂设计模式.PNG

      此时,客户端程序类与IFood接口的子类没有任何的关联,所有的关联都是通过Factory类来完成的,而在程序运行的时候,可以通过初始化参数来进行要使用子类的定义:
      	java JavaDemo bread
      	java JavaDemo mild
      

课时90:代理设计模式

  • 代理设计模式的主要功能:可以帮助用户将所有的开发注意力只集中在核心业务功能的处理上。

代理设计.PNG

interface IEat {
    public void get();
}
class EatReal implements IEat {
    @Override
    public void get() {
        System.out.println("【真实主题】获得一份食物。");
    }
}
class EatProxy implements IEat { // 服务代理
    private IEat eat; // 为吃而服务
    public EatProxy(IEat eat) { // 代理项
        this.eat = eat;
    }
    @Override
    public void get() {
        this.prepare();
        this.eat.get();
        this.clean();
    }
    public void prepare() {
        System.out.println("【代理主题】1、精心购买食材。");
        System.out.println("【代理主题】2、小心处理食材。");
    }
    public void clean() {
        System.out.println("【代理主题】3、收拾碗筷。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IEat eat = new EatProxy(new EatReal());
        eat.get();
    }
}
  • 代理设计模式的主要特点:
    • 一个接口提供由两个子类:
      • 真实业务操作类
      • 代理业务操作类
    • 如没有代理业务操作,则真实业务就无法进行。

课时91:抽象类与接口区别

  • 在实际的开发之中可以发现,抽象类和接口的定义形式是非常相似的,这一点从JDK1.8开始就特别明显,因为在JDK1.8里面,接口也可以定义default和static方法了。
NO区别抽象类接口
1定义关键字abstract class 抽象类名称 {}interface 接口名称 {}
2组成构造、普通方法、静态方法、全局常量、普通成员抽象方法、全局常量、普通方法、静态方法
3权限各种权限只能使用public
4子类使用子类通过extends关键字只能继承一个抽象类子类通过implements关键字可以实现多个接口
5两者关系可以实现若干个接口接口不允许继承抽象类,但允许继承多个父接口
  • 抽象类和接口的使用:

    • 抽象类或接口必须定义子类;
    • 子类一定要覆写抽象类或接口中的全部的抽象方法;
    • 抽象类或接口的对象实例化通过子类的向上转型来实现。
  • 当抽象类和接口都可以使用的情况下,要优先考虑接口,因为接口可以避免子类的单继承局限。

各个结构的设计关系.PNG

第21章:综合案例:抽象类与接口应用

课时92:案例分析一(获取类信息)

  • 抽象类与接口是Java里最为核心的概念,也是所有设计模式的综合体现。

  • 案例:

    • 定义一个ClassName接口,接口中只有一个抽象方法getClassName();
    • 设计一个类Company,该类实现接口ClassName中的方法getClassName(),功能是获取该类的类名称;
    • 编写应用程序使用Company类。
interface IClassName {
    public String getClassName();
}
class Company implements IClassName {
    @Override
    public String getClassName() {
        return "Company";
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IClassName ica = new Company();
        System.out.println(ica.getClassName());
    }
}

课时93:案例分析二(绘图处理)

  • 考虑一个表示绘图的标准,并且可以根据不同的图形来进行绘制。

绘图分析.PNG

interface IGraphical { // 定义绘图标准
    public void paint(); // 绘图
}
class Point {
    private double x;
    private double y;
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return this.x;
    }
    public double getY() {
        return this.y;
    }
}
class Triangle implements IGraphical {
    private Point[] x; // 保存第一条边的坐标
    private Point[] y; // 保存第二条边的坐标
    private Point[] z; // 保存第三条边的坐标
    public Triangle(Point[] x, Point[] y, Point[] z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    @Override
    public void paint() {
        System.out.println("绘制第一条边,开始坐标[" + this.x[0].getX() + ", " + this.x[0].getY() +"]," +
                "结束坐标[" + this.x[1].getX() + "," + this.x[1].getY() +"]");
        System.out.println("绘制第二条边,开始坐标[" + this.y[0].getX() + ", " + this.y[0].getY() +"]," +
                "结束坐标[" + this.y[1].getX() + "," + this.y[1].getY() +"]");
        System.out.println("绘制第三条边,开始坐标[" + this.z[0].getX() + ", " + this.z[0].getY() +"]," +
                "结束坐标[" + this.z[1].getX() + "," + this.z[1].getY() +"]");
    }
}
class Circle implements IGraphical {
    private double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
    @Override
    public void paint() {
        System.out.println("以半径为" + this.radius + "绘制圆形。");
    }
}
class Factory {
    public static IGraphical getInstance(String className, double ... args) {
        if ("triangle".equalsIgnoreCase(className)) {
            return new Triangle(
                    new Point[] {
                            new Point(args[0], args[1]), new Point(args[2], args[3]),
                    },
                    new Point[] {
                            new Point(args[4], args[5]), new Point(args[6], args[7]),
                    },
                    new Point[] {
                            new Point(args[8], args[9]), new Point(args[10], args[11]),
                    }
            );
        }
        else if ("circle".equalsIgnoreCase(className)) {
            return new Circle(args[0]);
        }
        else return null;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IGraphical iga = Factory.getInstance("triangle", 1.1,2.2,3.3,4.4,11.11,22.22,33.33,44.44,111.111,222.222,333.333,444.444);
        iga.paint();
        IGraphical igb = Factory.getInstance("circle", 88.11);
        igb.paint();
    }
}

课时94:案例分析三(图形)

  • 定义类Shape,用来表示一般二维图形。
    • 抽象方法:
      • area():计算形状的面积
      • perimeter():计算形状的周长
    • 子类:
      • 二维形状类(如矩形三角形、圆形、椭圆形等)

图形设计.PNG

abstract class AbstractShape {
    public abstract double area();
    public abstract double perimeter();
}
class Circle extends AbstractShape {
    private double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
    @Override
    public double area() {
        return 3.1415926 * this.radius * this.radius;
    }
    @Override
    public double perimeter() {
        return 2 * 3.1415926 * this.radius;
    }
}
class Rectangle extends AbstractShape {
    private double length;
    private double width;
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    @Override
    public double area() {
        return this.length * this.width;
    }
    @Override
    public double perimeter() {
        return 2 * (this.length + this.width);
    }
}
class Factory {
    public static AbstractShape getInstance(String className, double ... args) {
        if ("circle".equalsIgnoreCase(className)) return new Circle(args[0]);
        else if ("rectangle".equalsIgnoreCase(className)) return new Rectangle(args[0], args[1]);
        else return null;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        AbstractShape asa = Factory.getInstance("circle", 1.1);
        AbstractShape asb = Factory.getInstance("rectangle", 1.5, 10.2);
        System.out.println("【圆形】面积:" + asa.area() + ",周长:" + asa.perimeter());
        System.out.println("【矩形】面积:" + asb.area() + ",周长:" + asb.perimeter());
    }
}
  • 使用工厂设计模式完全隐藏了实现的子类。

第22章:泛型

课时95:泛型问题引出

  • 泛型是从JDK1.5之后追加的,其主要目的是为了解决ClassCastException的问题,在进行对象的向下转型时可能存在安全隐患,而Java希望通过泛型来慢慢解决此问题。

  • 案例:定义一个描述x与y坐标的处理类

    • 在这个类中允许保存三类数据:
      • 整型数据:x=10,y=20
      • 浮点型数据:x=10.1,y=20.9
      • 字符串型数据:x=东经120度,y=北纬30度
    • 于是x、y属性的类型就需要利用Object类来进行定义,那么就存在如下的转型关系:
      • 整型数据:基本数据类型 → 包装为Integer类对象 → 自动向上转型为Object
      • 浮点型数据:基本数据类型 → 包装为Double类对象 → 自动向上转型为Object
      • 字符串型数据:String类对象 → 自动向上转型为Object
    class Point {
        private Object x;
        private Object y;
        public Object getX() {
            return x;
        }
        public void setX(Object x) {
            this.x = x;
        }
        public Object getY() {
            return y;
        }
        public void setY(Object y) {
            this.y = y;
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            Point point = new Point();
            // 第一步:根据需求进行内容的设置
            point.setX(10); // 自动装箱
            point.setY(20); // 自动装箱
            // 第二步:获取数据
            int x = (Integer)point.getX();
            int y = (Integer)point.getY();
            System.out.println("X坐标:" + x + ",Y坐标:" + y);
        }
    }
    
    • 问题:
    public class JavaDemo {
        public static void main(String[] args) {
            Point point = new Point();
            // 第一步:根据需求进行内容的设置
            point.setX(10); // 自动装箱
            point.setY("北纬30度"); // 自动装箱
            // 第二步:获取数据
            int x = (Integer)point.getX();
            int y = (Integer)point.getY(); // 此时编译不会出现问题,运行时才会报ClassCastException异常
            System.out.println("X坐标:" + x + ",Y坐标:" + y);
        }
    }
    

课时96:泛型基本定义

  • 避免项目中出现“ClassCastException”最好的做法是可以直接回避掉对象的强制转换,所以在JDK1.5之后提供了泛型技术。

  • 泛型的本质:类中的属性、方法的参数与返回值的类型,可以在对象实例化时动态决定。

  • 在类定义时,就需要明确地定义占位符(泛型标记)。

    class Point <T> { // T是Type地简写,可以定义多个泛型
        private T x;
        private T y;
        public T getX() {
            return x;
        }
        public void setX(T x) {
            this.x = x;
        }
        public T getY() {
            return y;
        }
        public void setY(T y) {
            this.y = y;
        }
    }
    

    此时Point类中的x与y属性的数据类型并不确定,而是由外部来决定。

  • 关于默认的泛型类型:为兼容之前项目的代码,所以如不设置泛型类型,则自动使用Object作为默认类型。但编译中会出现警告信息。

  • 泛型定义完成后,可以在实例化对象时进行泛型的设置,而一旦设置之后,属性的类型就与当前的对象直接绑定了。

    public class JavaDemo {
        public static void main(String[] args) {
            Point<Integer> point = new Point<>();
            // 第一步:根据需求进行内容的设置
            point.setX(10); // 自动装箱
            point.setY("北纬30度"); // 此时编译则会报错
            // 第二步:获取数据
            int x = (Integer)point.getX();
            int y = (Integer)point.getY();
            System.out.println("X坐标:" + x + ",Y坐标:" + y);
        }
    }
    
    public class JavaDemo {
        public static void main(String[] args) {
            Point<Integer> point = new Point<Integer>(); // 此时,point这个对象里,所有的T都被替换成了Integer
            // 第一步:根据需求进行内容的设置
            point.setX(10); // 自动装箱
            point.setY(20); // 自动装箱
            // 第二步:获取数据
            int x = point.getX();
            int y = point.getY();
            System.out.println("X坐标:" + x + ",Y坐标:" + y);
        }
    }
    
  • 使用泛型的注意事项:

    • 泛型中只允许设置引用类型,如果要操作基本数据类型则必须使用包装类;
    • 从JDK1.7开始,泛型对象实例化可以简化为”Point point = new Point<>();“。

课时97:泛型通配符

  • 虽然泛型帮助开发者解决了一系列由于对象强制转换而带来的安全隐患问题,但泛型也带来了新的问题:引用传递问题。

  • 可以使用通配符”?“来解决泛型带来的引用传递的问题。

    class Message<T> {
        private T content;
        public T getContent() {
            return content;
        }
        public void setContent(T content) {
            this.content = content;
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            Message<Integer> msgA = new Message<>();
            Message<String> msgB = new Message<>();
            msgA.setContent(110);
            fun(msgA);
            msgB.setContent("pikaqiu");
            fun(msgB);
        }
        public static void fun(Message<?> temp) {
            System.out.println(temp.getContent());
        }
    }
    
  • 通配符”?“:

    • “? extends 类”:设置泛型的上限

      • 如定义”? extends Number“:表示该泛型类型只允许设置Number或Number的子类。

        public class JavaDemo {
            public static void main(String[] args) {
                Message<Integer> msgA = new Message<>();
                Message<String> msgB = new Message<>();
                msgA.setContent(110);
                fun(msgA);
                msgB.setContent("pikaqiu");
                fun(msgB); // 编译时会报错
            }
            public static void fun(Message<? extends Number> temp) { // 设置泛型上限
                System.out.println(temp.getContent());
            }
        }
        
    • “? super 类”:设置泛型的下限

      • 如定义”? super String“:表示该泛型类型只允许设置String或String的父类。

        public class JavaDemo {
            public static void main(String[] args) {
                Message<Integer> msgA = new Message<>();
                Message<String> msgB = new Message<>();
                msgA.setContent(110);
                fun(msgA);
                msgB.setContent("pikaqiu");
                fun(msgB); // 编译时会报错
            }
            public static void fun(Message<? super String> temp) { // 设置泛型下限
                System.out.println(temp.getContent());
            }
        }
        

课时98:泛型接口

interface IMessage<T> {
	public String echo(T t);
}
  • 泛型接口子类的实现方式:

    • 实现方式一:在子类中设置泛型定义

      class MessageImpl<S> implements IMessage<S> {
      	public String echo(S t) {
      		return "【ECHO】" + t;
      	}
      }
      public class JavaDemo {
          public static void main(String[] args) {
          	IMessage<String> msg = new MessageImpl<>();
          	System.out.println(msg.echo("pikaqiu"));
          }
      }
      
    • 实现方式二:在子类实现父接口时直接定义具体泛型类型

      class MessageImpl<S> implements IMessage<String> {
      	public String echo(String t) {
      		return "【ECHO】" + t;
      	}
      }
      public class JavaDemo {
          public static void main(String[] args) {
          	IMessage<String> msg = new MessageImpl();
          	System.out.println(msg.echo("pikaqiu"));
          }
      }
      

课时99:泛型方法

  • 泛型方法不一定非要出现在泛型类中。

    public class JavaDemo {
        public static void main(String[] args) {
        	Integer[] num = fun(1, 2, 3);
        	for(int temp : num) {
        		System.out.print(temp + " ");
        	}
        }
        public static<T> T[] fun(T ... args) {
        	return args;
        }
    }
    
  • 使用泛型的工厂设计模式:

    interface IMessage {
        void send(String str);
    }
    class MessageImpl implements IMessage {
        @Override
        public void send(String str) {
            System.out.println("消息发送:" + str);
        }
    }
    class Factory {
        public static <T>T getInstance(String className) {
            if ("MessageImpl".equalsIgnoreCase(className))
                return (T)new MessageImpl();
            else return null;
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            IMessage msg = Factory.getInstance("MessageImpl");
            msg.send("pikaqiu");
        }
    }
    

第23章:包的定义及使用

课时100:包的定义

  • 在实际的项目开发过程中,要一直存在包的概念,利用包可以实现类的包装,以后所有的类都必须放在包里面。

  • 当项目中有多人协同开发时,就有可能产生类的重名定义。

  • 包相当于操作系统中的目录:同一个目录中不允许两个同名文件存在。

  • 示例:

    • 编写Hello.java文件:

      package cn.pjh.demo; // 定义包,其中.表示分割子目录(子包)
      public class Hello {
      	public static void main() {
      		System.out.println("Hello World!");
      	}
      }
      
    • 打包编译处理:javac -d . Hello.java

      • “-d”:表示要生成目录,而目录的结构就是package定义的结构;
      • “.”:表示在当前所在的目录中生成程序类文件;
    • 在程序执行时,一定要带着包名执行程序类:java cn.pjh.demo.Hello

  • 完整的类名称时“包名.类名”。

课时101:包的导入

  • 利用包就可以将不同功能的类保存在不同的包之中,如果类之间需要互相调用,那么就需要使用import语句来导入其它包中的程序类。

  • 示例:

    package cn.pjh.util; // 定义包
    public class Message {
    	public String getContent() {
    		return "pikaqiu";
    	}
    }
    
    package cn.pjh.test; // 定义包
    import cn.pjh.util.Message; // 导入包
    public class TestMessage {
    	public static void main(String[] args) {
    		Message msg = new Message();
    		System.out.println(msg.getContent());
    	}
    }
    
  • 编译命令:javac -d . *.java(由编译器自己决定编译顺序。)

  • 注意:关于public class与class定义的区别?

    • public class:类名称必须与文件名称保持一致,一个*.java文件里面只允许有一个public class,同时如果一个类需要被其它包所使用,那么这个类一定要定义为public class。
    • class:类名称可以与文件名称不一致,一个*.java文件里面可以有多个class,编译后将形成多个*.class文件,但这些类只能被本包访问,外包无法访问。
    • 在实际开发中,往往一个*.java源代码文件里只会提供有一个程序类,而这个程序类一般都使用public class定义。
  • 程序类中定义的包名必须采用小写字母的形式来定义。

  • 可使用“包名.”的形式来导入该包下的所有类,但这并不表示要进行全部的加载,会根据需要进行加载,所以使用“”*“和使用具体类的性能是完全相同的。

  • 由于导入的不同包中,也可能存在相同类名,因此在调用的时候应该使用类的完整名称,否则编译时会出现错误。

课时102:静态导入

  • 示例:

    package cn.pjh.util;
    public class MyMath {
    	public static int add(int ... args) {
    		int sum = 0;
    		for (int temp : args) {
    			sum += temp;
    		}
    		return sum;
    	}
    	public static int sub(int x, int y) {
    		return x - y;
    	}
    }
    
    • 原始导入方法:

      package cn.pjh.test;
      import cn.pjh.util.MyMath;
      public class TestMath {
      	public static void main(String[] args) {
      		System.out.println(MyMath.add(10, 20, 30));
      		System.out.println(MyMath.sub(30, 20));
      	}
      }
      

      从JDK1.5开始,对于类中全部由静态方法提供的特殊类时可以采用静态导入处理形式的。

    • 静态导入:

      package cn.pjh.test;
      import static cn.pjh.util.MyMath.*;
      public class TestMath {
      	public static void main(String[] args) {
      		System.out.println(add(10, 20, 30));
      		System.out.println(sub(30, 20));
      	}
      }
      

      当使用静态导入后,就好比该方法是直接定义在主类中的,可以由主方法直接调用。

课时103:生成jar文件

  • jar文件:把开发完成后大量的*.class文件管理起来的压缩结构。

  • 可使用jdk中提供的jar命令将文件打包成jar文件,可在命令行中输入“jar”来查看所有jar命令。

  • 示例:

    1. 定义一个程序类:

      package cn.pjh.util;
      public class Message {
      	public String getContent() {
      		return "pikaqiu";
      	}
      }
      
    2. 对程序打包编译:

      1. 编译:javac -d . Message.java。生成cn的包,包里面有相应子类与*.class文件
      2. 打包:jar -cvf pjh.jar cn
        • “-c”:创建一个新的jar文件;
        • “-v”:得到一个详细输出;
        • “-f”:设置要生成的jar文件名称
    3. 配置CLASSPATH(每一个jar文件都是一个独立的程序路径,如果要想在Java程序中使用此路径,则必须通过CLASSPATH进行配置。)

      • 如:SET CLASSPATH =d:\demo\pjh.jar
    4. 建立测试类,直接导入Message类并且调用方法:

      package cn.pjh.test;
      public class TestMessage {
      	public static void main(String[] args) {
      		cn.pjh.util.Message msg = new cn.pjh.util.Message(); // 实例化类对象
      		System.out.println(msg.getContent());
      	}
      }
      
    5. 正常编译TestMessage类并使用这个类:

      1. 编译程序类:javac -d . TestMessage.java
      2. 解释程序:java cn.pjh.test.TestMessage
  • 出现“java.lang.NoClassDefFoundError”的错误:只有一种情况,*.jar包没有配置正确(可能为CLASSPATH发生了改变,类无法加载到了)。

  • JDK1.9之后出现的模块化操作:

    • 在JDK1.9之前,实际上提供的是一个所有类的*.jar文件(rt.jar、tools.jar),所以只要启动了Java的虚拟机,就需要加载几十兆的类文件;
    • 在JDK1.9之后,提供了一个模块化的设计,将原本很大的要加载的一个*.jar文件变成了若干个模块文件,这样在启动的时候可以根据程序加载指定的模块(模块中有包),就可以实现启动速度变快的效果。

课时104:系统常用包

  • Java的类库:
    • Java自身提供的(除了JDK提供的之外还会有一些标准);
      • java.lang:String、Number、Object……(在JDK1.1后自动默认导入)
      • java.lang.reflect:反射机制处理包,所有的设计从此开始;
      • java.util:工具类定义包,包括数据结构的定义;
      • java.io:输入与输出流操作包;
      • java.net:网络程序开发包;
      • java.sql:数据库编程包;
      • java.applet:Java最原始的适用形式,直接嵌套在网页上执行的程序类(现在的程序已经以Application为主了,也就是有主方法的程序);
      • java.awt(重量级)、javax.swing(轻量级):图形界面开发包(GUI)
    • 由第三方厂商提供的。

课时105:访问控制权限

  • 在面向对象的开发过程中,有三大主要特点:封装、继承、多态。而对于封装性而言,主要的实现依靠的就是访问控制权限。
  • 访问控制权限:
    • private
    • default(不写)
    • protected
    • public
No访问范围privatedefaultprotectedpublic
1同包中的同类
2同包中的不同类
3不同包的子类
4不同包的所有类
  • 参考方案(90%)
    • 属性使用private;
    • 方法使用public。

第24章:UML图形(略)

第25章:单例设计模式

课时109:单例设计

  • 单例设计模式:只保留一个实例化对象的设计模式。

  • 案例(饿汉式单例设计演化):

    • 非单例设计:

      class Singleton {
          public void print() {
              System.out.println("pikaqiu");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
          	// 可产生多个实例化对象
              Singleton instanceA = new Singleton();
              Singleton instanceB = new Singleton();
              Singleton instanceC = new Singleton();
              instanceA.print();
              instanceB.print();
              instanceC.print();
          }
      }
      
    • 单例演化:

      • 如果一个类只允许提供有一个实例化对象,那么为了避免有新的实例化对象产生,则一定要将构造方法私有化;

        class Singleton {
         private Singleton() {} // 构造方法私有化
         public void print() {
         	System.out.println("pikaqiu");
         }
        }
        public class JavaDemo {
        	public static void main(String[] args) {
        		Singleton instance = null; // 声明对象
        		instance = new Singleton(); // 错误:Singleton()可以在Singleton中访问private
        		instance.print();
        	}
        }
        
      • 而private私有化的特点是,不能在类外部访问,却能在类内部访问,那么则可以使用本类属性来进行实例化;

        class Singleton {
            Singleton instance = new Singleton(); // 本类属性可调用私有化的构造方法
            private Singleton() {} // 构造方法私有化
            public void print() {
                System.out.println("pikaqiu");
            }
        }
        public class JavaDemo {
            public static void main(String[] args) {
                Singleton instance = null; // 声明对象
        //        instance = new Singleton();
        //        instance.print();
            }
        }
        
      • 而普通属性在没有实例化对象时也是无法访问的,那么如何在没有实例化对象时获取属性呢?这就只有static属性可以做到了;

        class Singleton {
            static Singleton instance = new Singleton(); // 静态属性,无需实例化对象就可以调用
            private Singleton() {} // 构造方法私有化
            public void print() {
                System.out.println("pikaqiu");
            }
        }
        public class JavaDemo {
            public static void main(String[] args) {
                Singleton instance = null; // 声明对象
                instance = Singleton.instance;
                instance.print(); // 正常输出
            }
        }
        
      • 而属性都应该进行封装,所以该属性应该通过static方法来进行获取;

        class Singleton {
            private static Singleton instance = new Singleton(); // 封装静态属性
            private Singleton() {} // 构造方法私有化
            public static Singleton getInstance() { // 静态方法返回封装了的静态属性
                return instance;
            }
            public void print() {
                System.out.println("pikaqiu");
            }
        }
        public class JavaDemo {
            public static void main(String[] args) {
                Singleton instance = null; // 声明对象
                instance = Singleton.getInstance();
                instance.print(); // 正常输出
            }
        }
        
      • 然而如果想要实例化对象只有一个,则需要使用final。

        // 饿汉式
        class Singleton {
            private static final Singleton INSTANCE = new Singleton(); // 私有的静态常量
            private Singleton() {} // 构造方法私有化
            public static Singleton getInstance() { // 静态方法返回私有的静态属性
                return INSTANCE;
            }
            public void print() {
                System.out.println("pikaqiu");
            }
        }
        public class JavaDemo {
            public static void main(String[] args) {
                Singleton instance = null; // 声明对象,并实例化
                instance = Singleton.getInstance();
                instance.print(); // 正常输出
            }
        }
        

单例设计.PNG

  • 分类:

    • 饿汉式:在系统加载类时自动提供类的实例化对象。(声明对象时就实例化)

    • 懒汉式:在第一次使用类时提供类的实例化对象。(首次使用时才实例化)

      // 懒汉式
      class Singleton {
          private static Singleton instance; // 去掉final,因为final会使得instance不可更改
          private Singleton() {} // 构造方法私有化
          public static Singleton getInstance() {
              if (null == instance) { // 第一次使用
                  instance = new Singleton(); // 实例化对象
              }
              return instance;
          }
          public void print() {
              System.out.println("pikaqiu");
          }
      }
      public class JavaDemo {
          public static void main(String[] args) {
              Singleton instance = null; // 声明对象
              instance = Singleton.getInstance(); // 第一次调用时实例化
              instance.print(); // 正常输出
          }
      }
      
  • 步骤总结:

    • 构造方法私有化
    • 使用属性来调用私有化的构造方法
    • 属性需要为private,以在未实例化对象时就可调用
    • 属性需要封装,则需要使用static方法来进行调用
    • 由于只需要实例化对象一次,因此属性需要加final(饿汉式)
    • 在static方法里判断是否已实例化对象,如没有,才进行实例化,否则直接返回即可(懒汉式)

课时110:多例设计

  • 多例设计模式:可以保留有多个实例化对象的设计模式。
class Color {
    private static final Color RED = new Color("红色"); // 私有的静态常量
    private static final Color GREEN = new Color("绿色"); // 私有的静态常量
    private static final Color BLUE = new Color("蓝色"); // 私有的静态常量
    private String title;
    private Color(String title) { // 构造方法私有化
        this.title = title;
    }
    public static Color getInstatnce(String color) { // 获取静态常量的静态方法
        switch(color) {
            case "red": return RED;
            case "green": return GREEN;
            case "blue": return BLUE;
            default: return null;
        }
    }
    public String toString() {
        return this.title;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Color color = Color.getInstatnce("green");
        System.out.println(color);
    }
}
  • 多例与单例的区别:属性有多个。

第26章:枚举

课时111:定义枚举类

  • 很多编程语言都会提供有枚举的概念,但Java一直到JDK1.5之后才提出了枚举的概念。
  • 枚举的主要作用:定义有限个数对象的一种结构(多例设计),枚举就属于多例设计,但结构比多例设计更简单。
  • 枚举的实现:enum关键字。
enum Color { // 枚举类
    RED, GREEN, BLUE; // 实例化对象
}
public class JavaDemo {
    public static void main(String[] args) {
        Color color = Color.RED; // 获取实例化对象
        System.out.println(color);
    }
}
  • 多例设计与枚举设计虽然可以实现相同的功能,但使用枚举可以在程序编译时就判断所使用的实例化对象是否存在。

  • 可以使用values()方法来获取所有的枚举对象:

    enum Color { // 枚举类
        RED, GREEN, BLUE; // 实例化对象
    }
    public class JavaDemo {
        public static void main(String[] args) {
            for (Color color : Color.values()) {
                System.out.println(color);
            }
        }
    }
    
  • 可以在switch中进行枚举项的判断:

    enum Color { // 枚举类
        RED, GREEN, BLUE; // 实例化对象
    }
    public class JavaDemo {
        public static void main(String[] args) {
            Color color = Color.RED;
            switch(color) { // 支持使用枚举项
                case RED:
                    System.out.println("红色");
                    break;
                case GREEN:
                    System.out.println("绿色");
                    break;
                case BLUE:
                    System.out.println("蓝色");
                    break;
            }
        }
    }
    

课时112:Enum类

  • 严格意义上来讲,枚举并不算一种新的结构,它的本质是一个类,而这个类默认会继承Enum类。

  • Enum类的定义:

    public abstract class Enum<E extends Enum<E>>
    extends Object
    implements Comparable<E>, Serializable
    
  • Enum类的方法:

    No方法名称类型描述
    1protected Enum(String name, int ordinal)构造
    2public final String name()普通获取对象名字
    3public final int ordinal()普通获取对象序号(由定义顺序决定)
    enum Color { // 枚举类
        RED, GREEN, BLUE; // 实例化对象
    }
    public class JavaDemo {
        public static void main(String[] args) {
            for (Color color : Color.values()) {
                System.out.println(color.ordinal() + "-" + color.name());
            }
        }
    }
    
  • 面试题:请解释enum与Enum的区别?

    • enum:是从JDK1.5之后提供的一个关键字,用于定义枚举类;
    • Enum:是一个抽象类,使用enum关键字定义的类默认继承此类。

课时113:定义枚举结构

  • 由于枚举本身就属于多例设计模式,因此在枚举类中也依然可以定义构造方法、普通方法、属性等。
  • 但请注意!枚举类中的构造方法不能采用非私有化定义。
enum Color { // 枚举类
    RED("红色"), GREEN("绿色"), BLUE("蓝色"); // 枚举对象要写在首行
    private String title; // 定义属性
    private Color(String title) {
        this.title = title;
    }
    public String toString() {
        return this.title;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        for (Color color : Color.values()) {
            System.out.println(color.ordinal() + "-" + color.name() + "-" + color);
        }
    }
}
  • 在枚举类中也可以实现接口的继承:
interface IMessage {
    public String getMessage();
}
enum Color implements IMessage { // 枚举类
    RED("红色"), GREEN("绿色"), BLUE("蓝色"); // 枚举对象要写在首行
    private String title; // 定义属性
    private Color(String title) {
        this.title = title;
    }
    public String toString() {
        return this.title;
    }
    @Override
    public String getMessage() {
        return this.title;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = Color.RED;
        System.out.println(msg.getMessage());
    }
}
  • 在枚举类中可以直接定义抽象方法,但要求每个枚举对象都要独立覆写此方法:
enum Color { // 枚举类
    RED("红色") {
        public String getMessage() {
            return this.toString();
        }
    }, GREEN("绿色") {
        public String getMessage() {
            return this.toString();
        }
    }, BLUE("蓝色") {
        public String getMessage() {
            return this.toString();
        }
    }; // 枚举对象要写在首行
    private String title; // 定义属性
    private Color(String title) {
        this.title = title;
    }
    public String toString() {
        return this.title;
    }
    public abstract String getMessage();
}
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println(Color.RED.getMessage());
    }
}
  • 枚举的定义是非常灵活的,但在实际的使用中,枚举更多情况下还是建议只定义实例对象。

课时114:枚举应用案例

  • 定义一个Person类,其中的性别属性使用枚举定义:
enum Sex {
    MALE("男"), FEMALE("女");
    private String title;
    private Sex(String title) {
        this.title = title;
    }
    public String toString() {
        return this.title;
    }
}
class Person {
    private String name;
    private int age;
    private Sex sex;
    public Person(String name, int age, Sex sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public String toString() {
        return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.sex;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println(new Person("张三", 20, Sex.MALE));
    }
}

第27章:异常的捕获与处理

课时115:认识异常对程序的影响

  • Java语言提供的最为强大的支持就在于异常的处理操作上。
  • 异常:指导致程序中断执行的一种指令流。
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        System.out.println("【2】****** 数学计算:" + (10 / 2) + " ******");
        System.out.println("【3】****** 程序执行结束 ******");
    }
}
  • 在程序正常执行时,所有的程序会按照既定的结构从头到尾开始执行。
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        System.out.println("【2】****** 数学计算:" + (10 / 0) + " ******");
        System.out.println("【3】****** 程序执行结束 ******");
        // 执行后输出:
        // 【1】****** 程序开始执行 ******
		// Exception in thread "main" java.lang.ArithmeticException: / by zero
    }
}
  • 在出现错误后,整个的程序将不会按照既定的方式进行执行,而是中断了执行。

  • 为了保证程序出现了非致命错误,程序依然可以正常完成,所以就需要由一个完善的异常处理机制,以保证程序的顺利执行。

课时116:处理异常

  • 进行异常的处理,可以使用try、catch、finally关键字,处理结构如下:
try {
	// 可能出现异常的语句
}
[catch(异常类型 异常对象) {
	// 异常处理的语句
} catch(异常类型 异常对象) {
	// 异常处理的语句
} catch(异常类型 异常对象) {
	// 异常处理的语句
} ... ]
[finally {
	// 不管异常是否处理都要执行的语句
}]

在此格式中,可以使用的组合为:try … catch、try … catch … finally、try … finally。

  • 处理异常:
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            System.out.println("【2】****** 数学计算:" + (10 / 0) + " ******"); // 会出现异常的语句
        } catch (ArithmeticException e) {
            System.out.println("【C】处理异常:" + e); // 处理异常
        }
        System.out.println("【3】****** 程序执行结束 ******");
        // 执行后输出:
        // 【1】****** 程序开始执行 ******
		// 【C】处理异常:java.lang.ArithmeticException: / by zero
		// 【3】****** 程序执行结束 ******
    }
}

此时,即便发生了异常,程序也可以正常执行结束,但是此时在进行异常处理时直接输出的时一个异常类的对象,那么该对象直接打印(调用toString())所得到的异常信息并不完整,可以使用异常类中提供的printStackTrace()方法:

public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            System.out.println("【2】****** 数学计算:" + (10 / 0) + " ******"); // 会出现异常的语句
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 打印异常的完整信息
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}
  • 对于异常的处理,在最后也可以追加一个finally程序块,表示异常处理后的出口,不管是否出现异常都执行:
// 有异常
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            System.out.println("【2】****** 数学计算:" + (10 / 0) + " ******"); // 有异常
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } finally {
            System.out.println("【F】不管是否出现异常,都会执行。");
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}
// 无异常
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            System.out.println("【2】****** 数学计算:" + (10 / 1) + " ******"); // 无异常
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } finally {
            System.out.println("【F】不管是否出现异常,都会执行。");
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}

课时117:处理多个异常

  • 当程序中可能会产生若干个异常时,可以使用多个catch来进行异常的捕获。
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            int x = Integer.parseInt(args[0]);
            int y = Integer.parseInt(args[1]);
            System.out.println("【2】****** 数学计算:" + (x / y) + " ******");
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } finally {
            System.out.println("【F】不管是否出现异常,都会执行。");
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}

此时程序可能产生三类异常:

  • 【未处理】程序执行时没有输入初始化参数(java JavaDemo):java.lang.ArrayIndexOutOfBoundsException
  • 【未处理】输入的数据不是数字(java JavaDemo a b):java.lang.NumberFormatException
  • 【已处理】输入的被除数为0(java JavaDemo 10 0):java.lang.ArithmeticException

执行结果如下:

处理多个异常.PNG

所以即使有了异常处理语句,但如果没有进行正确的异常捕获,那么程序也会导致中断(finally的代码依然执行)

  • 处理多个异常:
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            int x = Integer.parseInt(args[0]);
            int y = Integer.parseInt(args[1]);
            System.out.println("【2】****** 数学计算:" + (x / y) + " ******");
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } catch (NumberFormatException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace(); // 打印异常的完整信息
        } finally {
            System.out.println("【F】不管是否出现异常,都会执行。");
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}

课时118:异常处理流程

异常处理流程.PNG

  1. 在程序运行的过程之中才会产生异常,而一旦产生异常之后,将自动进行指定类型的异常类对象的实例化处理;
  2. 此时程序中:
    1. 没有提供异常处理:采用JVM默认的异常处理方式:首先进行异常信息的打印,而后直接退出当前的程序;
    2. 有提供异常处理:这个产生这个异常类的实例化对象则会被try语句所捕获,捕获后将与catch中的异常类型进行依次地对比,如没有匹配catch,则不进行处理。
  3. 如有finally语句,则不管异常是否处理,都执行finally语句。而执行完finally后,再判断是否处理了异常:
    1. 已处理:则继续向后执行代码;
    2. 未处理:则交由JVM进行默认的处理。
  • 在整个异常处理流程中,实际上操作的还是一个异常类的实例化对象,所以这个异常类的实例化对象的类型就成为了理解异常处理的核心关键所在。

    ArithmeticException的包:
    java.lang.Object
    	java.lang.Throwable
    		java.lang.Exception
    			java.lang.RuntimeException
    				java.lang.ArithmeticException
    
    NumberFormatException的包:
    java.lang.Object
    	java.lang.Throwable
    		java.lang.Exception
    			java.lang.RuntimeException
    				java.lang.IllegalArgumentException
    					java.lang.NumberFormatException
    
    ArrayIndexOutOfBoundsException的包:
    java.lang.Object
    	java.lang.Throwable
    		java.lang.Exception
    			java.lang.RuntimeException
    				java.lang.IndexOutOfBoundsException
    					java.lang.ArrayIndexOutOfBoundsException
    

    可以发现,程序中处理异常最大的类型就是Throwable类。

  • Throwable类:

    • Error类:程序还未执行时出现的错误,开发者无法处理。
    • Exception类:程序执行中出现的异常,开发者可以处理。(真正开发中需要关注的)
  • 由于异常产生时,会产生异常的实例化对象,那么按照对象的引用原则,其可以自动向父类转型,所以实际上所有的异常都可以使用Exception来处理:

public class JavaDemo {
    public static void main(String[] args) {
        System.out.println("【1】****** 程序开始执行 ******");
        try {
            int x = Integer.parseInt(args[0]);
            int y = Integer.parseInt(args[1]);
            System.out.println("【2】****** 数学计算:" + (x / y) + " ******");
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常的完整信息
        } finally {
            System.out.println("【F】不管是否出现异常,都会执行。");
        }
        System.out.println("【3】****** 程序执行结束 ******");
    }
}

但如果都用Exception来处理异常,会导致描述的错误信息不明确,所以分开处理异常是更加明确的处理方式。

  • 在进行多个异常处理时,需要把捕获范围大的异常放在捕获范围小的异常后面。

课时119:throws关键字

  • 在定义方法时,如方法中可能产生异常,则应该明确地告诉使用者。
  • 异常类型的标注:在方法声明上使用throws关键字。
class MyMath {
    // 下面这段代码可能会产生异常,如果产生异常,则需要调用处进行处理
    public static int div(int x, int y) throws Exception {
        return x / y;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        System.out.println(MyMath.div(10, 2)); // 编译时则会出现“Error:(9, 38) java: 未报告的异常错误java.lang.Exception; 必须对其进行捕获或声明以便抛出”
    }
}
class MyMath {
    // 下面这段代码可能会产生异常,如果产生异常,则需要调用处进行处理
    public static int div(int x, int y) throws Exception {
        return x / y;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        try {
            System.out.println(MyMath.div(10, 2)); // 增加try...catch,可正常输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 主方法也是个方法,所以主方法也可以继续向上抛出异常:
class MyMath {
    // 下面这段代码可能会产生异常,如果产生异常,则需要调用处进行处理
    public static int div(int x, int y) throws Exception {
        return x / y;
    }
}
public class JavaDemo {
    public static void main(String[] args) throws Exception { // 主方法继续向上抛出异常
        System.out.println(MyMath.div(10, 0)); // 执行时抛出ArithmeticException异常
    }
}
  • 如主方法向上抛出异常,那么就表示此异常交由JVM处理。

课时120:throw关键字

  • throw关键字:表示手工进行异常的抛出,即:将手工产生一个异常类的实例化对象,并且进行异常的抛出。
public class JavaDemo {
    public static void main(String[] args) {
        try {
            throw new Exception("自己抛着玩的异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// 执行时输出:java.lang.Exception: 自己抛着玩的异常
  • 面试题:请解释throw与throws的区别?
    • throw:是在代码块中使用的,用于手工进行异常对象的抛出;
    • throws:是在方法定义上使用的,用于将此方法中可能产生的异常明确的告诉调用处,需要由调用处进行处理。

课时121:异常处理模型

class MyMath {
    public static int div(int x, int y) throws Exception { // 向上抛出异常
        int temp = 0;
        System.out.println("****** 【START】除法计算开始。");
        try {
            temp = x / y;
        } finally {
            System.out.println("****** 【END】除法计算结束。");
        }
        return temp;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        try {
            System.out.println(MyMath.div(10, 0));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(个人发现的小知识点:如向上抛出异常,则运行时会先执行抛出异常中的finally块,再执行调用处的catch块,再执行调用处的finally块。)

课时122:RuntimeException

  • 灵活可选的异常处理类:RuntimeException类,其子类可以不需要调用处强制进行处理

  • 面试题:请解释RuntimeException与Exception的区别?并列举出几个常见的RuntimeException异常。

    • RuntimeException是Exception的子类;
    • RuntimeException标注的异常可以不需要进行强制性处理,而其它Exception异常必须强制性处理。
    • 常见的RuntimeException异常:NumberFormatException、ClassCastException、NullPointException。

课时123:自定义异常类

  • 自定义异常的实现方案:
    • 继承Exception
    • 继承RuntimeException
class BombException extends Exception {
    public BombException(String msg) {
        super(msg);
    }
}
class Food {
    public static void eat(int num) throws BombException{
        if (10 < num) throw new BombException("吃太多了,肚子爆了");
        else System.out.println("正常吃,不怕吃胖。");
    }
}
public class JavaDemo {
    public static void main(String[] args) throws Exception {
        Food.eat(11);
    }
}
class BombException extends RuntimeException {
    public BombException(String msg) {
        super(msg);
    }
}
class Food {
    public static void eat(int num) throws BombException{
        if (10 < num) throw new BombException("吃太多了,肚子爆了");
        else System.out.println("正常吃,不怕吃胖。");
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Food.eat(11);
    }
}

课时124:assert断言

  • 从JDK1.4开始追加断言功能,用来确定代码执行到某行之后一定是所期待的结果。并不一定是准确的,也有可能出现偏差,但是这种偏差不应该影响程序的正常执行。
public class JavaDemo {
    public static void main(String[] args) {
        int x = 10;
        assert x == 100 : "x的内容不是100";
        System.out.println(x); // 输出:10
    }
}
  • 执行断言:在执行程序时加入参数-ea

    • 执行命令:java -ea JavaDemo

    • 执行结果:

      Exception in thread "main" java.lang.AssertionError: x的内容不是100
      	at JavaDemo.main(JavaDemo.java:4)
      

第28章:内部类

课时125:内部类基本概念

  • 内部类:在类的内部定义的其它类。
class Outer { // 外部类
    private String msg = "piakqiu"; // 私有成员属性
    public void fun() { // 普通方法
        Inner in = new Inner(); // 实例化内部类对象
        in.print(); // 调用内部类方法
    }
    class Inner { // 内部类
        public void print() {
            System.out.println(Outer.this.msg); // 打印外部类的私有属性
        }
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Outer out = new Outer(); // 实例化外部类对象
        out.fun(); // 调用外部类方法
    }
}
  • 从整体的代码结构上来讲,内部类的结构并不合理,所以内部类本身最大的缺陷在于破坏了程序的结构,但也有其优势。
// 不使用内部类
class Outer { // 外部类
    private String msg = "piakqiu"; // 私有成员属性
    public void fun() { // 普通方法
        // 思考五:需要将当前对象Outer传递到Inner类中
        Inner in = new Inner(this); // 实例化内部类对象
        in.print(); // 调用内部类方法
    }
    // 思考一:msg属性如果要被外部访问,则需要提供getter方法
    public String getMsg() {
        return this.msg;
    }

}
class Inner { // 内部类
    // 思考三:Inner这个类对象实例化时需要Outer类的引用
    private Outer out;
    // 思考四:应该通过Inner类的构造方法获取Outer类的对象
    public Inner(Outer out) {
        this.out = out;
    }
    public void print() {
        // 思考二:如果要想调用外部类中的getter方法,那么一定要有Outer类的对象
        System.out.println(this.out.getMsg()); // 打印外部类的私有属性
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Outer out = new Outer(); // 实例化外部类对象
        out.fun(); // 调用外部类方法
    }
}

可以看到,上述代码的主要目的就是为了让Inner内部类可以访问Outer外部类中的私有属性。

  • 内部类的优点:可以轻松访问外部类中的私有成员。

课时126:内部类相关说明

  • 内部类可以轻松访问外部类中的私有成员,同样外部类也可以轻松访问内部类中的私有成员:
class Outer { // 外部类
    private String msg = "piakqiu"; // 私有成员属性
    public void fun() { // 普通方法
        Inner in = new Inner(); // 实例化内部类对象
        in.print(); // 调用内部类方法
        System.out.println(in.info); // 访问内部类的私有属性
    }
    class Inner { // 内部类
        private String info = "今天天气不好,收衣服啦!";
        public void print() {
            System.out.println(Outer.this.msg); // 打印外部类的私有属性
        }
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Outer out = new Outer(); // 实例化外部类对象
        out.fun(); // 调用外部类方法
    }
}
  • 内部类与外部类之间私有操作的访问就不再需要通过setter/getter以及其它间接的方式来完成,可以直接进行操作。

  • 内部类也是一个类,所以外部依然可以产生内部类的实例化对象:

    外部类.内部类 内部类对象名称 = new 外部类().new 内部类();

    在内部类编译完成后,会自动形成一个“Outer I n n e r . c l a s s ” 的 文 件 , 其 中 “ Inner.class”的文件,其中“ Inner.class”这个符号换到程序之中就变为了“.”,所以内部类的全称为:“外部类.内部类”。

  • 抽象类与接口中都可以定义内部结构。

interface IChannel { // 定义接口
    public void send(IMessage msg); // 发送消息
    interface IMessage { // 内部接口
        public String getContent(); // 获取消息内容
    }
}
class ChannelImpl implements IChannel {
    public void send(IMessage msg) {
        System.out.println("发送消息:" + msg.getContent());
    }
    class MessageImpl implements IMessage {
        public String getContent() {
            return "pikaqiu";
        }
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IChannel channel = new ChannelImpl();
        channel.send(((ChannelImpl)channel).new MessageImpl());
    }
}
  • 内部抽象类可以定义在普通类、抽象类、接口内部:
interface IChannel { // 定义接口
    public void send(); // 发送消息
    abstract class AbstractMessage {
        public abstract String getContent();
    }
}
class ChannelImpl implements IChannel {
    public void send() {
        AbstractMessage msg = new MessageImpl();
        System.out.println("发送消息:" + msg.getContent());
    }
    class MessageImpl extends AbstractMessage {
        public String getContent() {
            return "pikaqiu";
        }
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IChannel channel = new ChannelImpl();
        channel.send();
    }
}
  • 接口内部进行接口实现:
interface IChannel {
    public void send();
    class ChannelImpl implements IChannel {
        public void send() {
            System.out.println("pikaqiu");
        }
    }
    public static IChannel getInstance() {
        return new ChannelImpl();
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IChannel channel = IChannel.getInstance();
        channel.send();
    }
}

课时127:static定义内部类

  • 如果在内部类上使用了static,那么这个内部类就变为了“外部类”,因为static定义的都是独立于类的结构,所以static定义的类就相当于时一个独立的程序类了。

  • 实例化static内部类对象:

    外部类.内部类 内部类对象名称 = new 外部类.内部类();

    class Outer {
        private static final String MSG = "pikaqiu";
        static class Inner {
            public void print() {
                System.out.println(Outer.MSG);
            }
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            Outer.Inner in = new Outer.Inner();
            in.print();
        }
    }
    
  • static定义内部类的形式并不常用,static定义内部接口的形式最为常用:

    interface IMessageWarp { // 消息包装类
        static interface IMessage {
            public String getContent();
        }
        static interface IChannel {
            public boolean connect(); // 消息的发送通道
        }
        public static void send(IMessage msg, IChannel channel) {
            if (channel.connect()) System.out.println(msg.getContent());
            else System.out.println("消息通道无法建立,消息发送失败!");
        }
    }
    class DefaultMessage implements IMessageWarp.IMessage {
        public String getContent() {
            return "pikaqiu";
        }
    }
    class NetChannel implements IMessageWarp.IChannel {
        public boolean connect() {
            return true;
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            IMessageWarp.send(new DefaultMessage(), new NetChannel());
        }
    }
    

课时128:方法中定义内部类

  • 在方法中定义的内部类:

    class Outer {
        private String msg = "pikaqiu";
        public void fun(long time) {
            class Inner { // 内部类
                public void print() {
                    System.out.println(Outer.this.msg);
                    System.out.println(time);
                }
            }
            new Inner().print(); // 方法中直接实例化内部类对象
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            new Outer().fun(2390239023L);
        }
    }
    
  • 内部类可以直接访问外部类中的私有成员,也可以直接访问方法中的参数,但对于方法中参数的直接访问,是从JDK1.8开始支持的。

  • 而在JDK1.8之前,如果方法中定义的内部类要想访问方法中的参数,则参数前必须追加final:

    class Outer {
        private String msg = "pikaqiu";
        public void fun(final long time) {
            final String info = "我很好";
            class Inner { // 内部类
                public void print() {
                    System.out.println(Outer.this.msg);
                    System.out.println(time);
                    System.out.println(info);
                }
            }
            new Inner().print(); // 方法中直接实例化内部类对象
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            new Outer().fun(2390239023L);
        }
    }
    

    而取消了final这样的限制,主要是为了其扩展的函数式编程准备的功能。

课时129:匿名内部类

  • 匿名内部类是一种简化的内部类处理形式,主要是使用在抽象类和接口的子类上。
interface IMessage {
    public void send(String str);
}
class MessageImpl implements IMessage {
    public void send(String str) {
        System.out.println(str);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl();
        msg.send("pikaqiu");
    }
}

上述代码中,如IMessage接口中的MessageImpl子类只使用唯一一次,那么则可以利用匿名内部类来实现:

interface IMessage {
    public void send(String str);
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = new IMessage() { // 匿名内部类
            public void send(String str) {
                System.out.println(str);
            }
        };
        msg.send("pikaqiu");
    }
}

(上述代码编译之后,会多生成一个JavaDemo$1.class的文件)

  • 有时为了更加方便地体现出匿名内部类的使用,往往利用静态方法来左一个内部的匿名内部类实现:

    interface IMessage {
        public void send(String str);
        public static IMessage getInstance() {
            return new IMessage() {
                public void send(String str) {
                    System.out.println(str);
                }
            };
        }
    }
    public class JavaDemo {
        public static void main(String[] args) {
            IMessage.getInstance().send("pikaqiu");
        }
    }
    
  • 与内部类相比,匿名内部类只是一个没有名字的,只能够使用一次的,并且结构固定的一个子类。

第29章:函数式编程

课时130:Lambda表达式

  • 从JDK1.8开始,为了简化代码的开发,专门提供Lambda表达式,利用此表达式可实现函数式编程。函数式编程较著名的语言:hackell、Scala。

  • 利用函数式编程可以避免面向对象编程中一些繁琐的处理:

    • 传统面向对象的处理:

      interface IMessage {
          public void send(String str);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IMessage msg = new IMessage() {
                  public void send(String str) {
                      System.out.println("消息发送:" + str);
                  }
              };
              msg.send("pikaqiu");
          }
      }
      

      实际上核心功能只有:“System.out.println(“消息发送:” + str);”

    • 使用Lambda表达式:

      interface IMessage {
          public void send(String str);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IMessage msg = (str)->{
                  System.out.println("发送消息:" + str);
              };
              msg.send("pikaqiu");
          }
      }
      
  • Lambda表达式的重要实现要求:SAM(Single Abstract Method,单个抽象方法)。

  • 函数式接口:接口里只提供有一个抽象方法。

  • 只有函数式接口才能使用Lambda表达式。

@FunctionalInterface // 函数式接口
interface IMessage {
    public void send(String str); // 抽象方法
    public default void print() {}; // 普通方法(接口中使用default定义的方法)
}
public class JavaDemo {
    public static void main(String[] args) {
        IMessage msg = (str)->{
            System.out.println("发送消息:" + str);
        };
        msg.send("pikaqiu");
        msg.print();
    }
}
  • Lambda表达式的格式:

    • 方法没有参数:()->{};

      @FunctionalInterface
      interface IMessage {
          public void send();
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IMessage msg = ()->{
                  System.out.println("发送消息");
              };
              msg.send(); // 输出:发送消息
          }
      }
      
    • 方法有参数:(参数, 参数)->{};

      @FunctionalInterface
      interface IMath {
          public int add(int x, int y);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IMath math = (t1, t2)->{
                  return t1 + t2;
              };
              System.out.println(math.add(10, 20)); // 输出:30
          }
      }
      
    • 只有一行语句返回:

      • 方法没有参数:()->语句;

        @FunctionalInterface
        interface IMessage {
            public void send();
        }
        public class JavaDemo {
            public static void main(String[] args) {
                IMessage msg = ()->System.out.println("发送消息");
                msg.send(); // 输出:发送消息
            }
        }
        
      • 方法有参数:(参数, 参数)->语句;

        @FunctionalInterface
        interface IMath {
            public int add(int x, int y);
        }
        public class JavaDemo {
            public static void main(String[] args) {
                IMath math = (t1, t2)->t1 + t2;
                System.out.println(math.add(10, 20)); // 输出:30
            }
        }
        

课时131:方法引用

  • 引用数据类型最大的特点就是可以进行内存的指向处理,但在传统的开发之中一直所使用的只是对象的引用操作,而从JDK1.8之后也提供有了方法的引用,即:不同的方法名称可以描述同一个方法。

  • 方法引用的形式:

    • 引用静态方法:

      类名称 :: static 方法名称

      如 String类 的 valueOf() 方法就是静态方法:public static String valuesOf(int i)

      @FunctionalInterface
      // P:参数;R:返回值。
      interface IFunction<P, R> {
          public R change(P p);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              // 这里的“String :: valueOf”是利用函数式编程给IFunction接口的change抽象方法定义的方法体
              IFunction<Integer, String> fun = String :: valueOf;
              String str = fun.change(100);
              System.out.println(str.length()); // 输出:3
          }
      }
      
    • 引用某个实例对象的方法:

      实例化对象 :: 普通方法的方法名称

      如 String类 的 toUpperCase() 方法就需要实例化对象后才可调用:public String toUpperCase()

      @FunctionalInterface
      // P:参数;R:返回值。
      interface IFunction<R> {
          public R upper();
      }
      public class JavaDemo {
          public static void main(String[] args) {
          	// 这里的“pikaqiu”为实例化对象
              IFunction<String> fun = "pikaqiu" :: toUpperCase;
              System.out.println(fun.upper()); // 输出:PIKAQIU
          }
      }
      
    • 引用特定类的方法:

      特定类 :: 普通方法的方法名称

      如调用 String类 的 compareTo() 方法:public int compareTo(String anotherString)

      @FunctionalInterface
      // P:参数;R:返回值。
      interface IFunction<P> {
          public int compare(P p1, P p2);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IFunction<String> fun = String :: compareTo;
              System.out.println(fun.compare("A", "a")); // 输出:-32
          }
      }
      
    • 引用构造方法(最具杀伤力):

      类名称 :: new

      class Person {
          private String name;
          private int age;
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
          public String toString() {
              return "姓名:" + this.name + ",年龄:" + this.age;
          }
      }
      @FunctionalInterface
      interface IFunction<R> {
          public R create(String s, int i);
      }
      public class JavaDemo {
          public static void main(String[] args) {
              IFunction<Person> fun = Person :: new;
              System.out.println(fun.create("张三", 20));
          }
      }
      
  • 方法引用,更多情况下只是弥补对于引用的支持功能。

课时132:内建函数式接口

  • 在系统中专门有一个 java.util.function 的开发包,可以直接使用里面提供的函数式接口。

  • 内建函数式接口:

    • 功能型函数式接口:接收参数,有返回。

      • 接口定义:

        @FunctionalInterface
        public interface Function<T,R> {
        	public R apply(T t)
        }
        
      • 接口使用:

        import java.util.function.*;
        public class JavaDemo {
            public static void main(String[] args) {
            	// public boolean startsWith(String prefix)
                Function<String, Boolean> fun = "**hello" :: startsWith;
                System.out.println(fun.apply("**")); // 输出:true
            }
        }
        
    • 消费型函数式接口:接收参数,无返回。

      • 接口定义:

        @FunctionalInterface
        public interface Consumer<T> {
        	public void accept(T t)
        }
        
      • 接口使用:

        import java.util.function.*;
        public class JavaDemo {
            public static void main(String[] args) {
            	// public void println(String x)
                Consumer<String> con = System.out :: println;
                con.accept("pikaqiu"); // 输出:pikaqiu
            }
        }
        
    • 供给型函数式接口:不接收参数,有返回。

      • 接口定义:

        @FunctionalInterface
        public interface Supplier<T> {
        	public T get()
        }
        
      • 接口使用:

        import java.util.function.*;
        public class JavaDemo {
            public static void main(String[] args) {
            	// public String toLowerCase()
                Supplier<String> sup = "PiKaQiu" :: toLowerCase;
                System.out.println(sup.get()); // 输出:pikaqiu
            }
        }
        
    • 断言型函数式接口:接收参数,返回boolean类型。

      • 接口定义:

        @FunctionalInterface
        public interface Predicate<T> {
        	public boolean test(T t)
        }
        
      • 接口使用:

        import java.util.function.*;
        public class JavaDemo {
            public static void main(String[] args) {
            	// public boolean equalsIgnoreCase(String anotherString)
                Predicate<String> pre = "pikaqiu" :: equalsIgnoreCase;
                System.out.println(pre.test("PIKAQIU")); // 输出:true
            }
        }
        
  • 个人整理:

类型接口名方法名是否接收参数返回类型
功能型Functionapply()任意
消费型Consumeraccept()
供给型Supplierget()×任意
断言型Predicatetest()boolean

第30章:链表的定义与使用

课时133:链表实现简介

  • 链表的本质:动态的对象数组,可随意实现若干个对象的存储。利用引用的逻辑关系来实现类似于数组的数据处理操作。
  • 链表结构:

火车车厢.PNG

链表结构.PNG

class Node<E> {
    private E data;
    private Node next;
    public Node(E data) {
        this.data = data;
    }
    public E getData() {
        return data;
    }
    public void setData(E data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        Node<String> node1 = new Node<>("火车头");
        Node<String> node2 = new Node<>("车厢一");
        Node<String> node3 = new Node<>("车厢二");
        Node<String> node4 = new Node<>("车厢三");
        node1.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node4);
        print(node1);
    }
    public static void print(Node<?> node) {
        if (null != node) { // 不为空节点
            System.out.println(node.getData());
            print(node.getNext()); // 递归调用
        }
    }
}

但是实际上,真实的使用者只关心数据的存储与获取,所以应作如下设计:

链表类的设计.PNG

课时134:数据增加

  • 通过之前的分析可以发现,在链表的操作过程中:
    • 为了避免转型的异常,应该使用泛型;
    • 应该设计一个链表标准的接口;
    • 在实现链表标准接口时,还应该通过Node类做出节点关系的描述。

链表增加数据.PNG

interface ILink<E> { // 设置泛型避免安全隐患
    public void add(E e); // 增加数据
}
class LinkImpl<E> implements ILink<E> {
    private class Node { // 保存节点
        private E data; // 保存数据
        private Node next; // 保存下一个节点
        public Node(E data) {
            this.data = data;
        }
        // 第一次调用:this = LinkImpl.root;
        // 第二次调用:this = LinkImpl.root.next;
        // 第三次调用:this = LinkImpl.root.next.next;
        public void addNode(Node newNode) { // 保存新节点
            // 当前节点的下一个节点为null,则保存当前节点为下一个节点
            if (null == this.next) this.next = newNode;
            // 当前节点的下一个节点不为null,则递归调用
            else this.next.addNode(newNode); //
        }
    }
    // ———————————— 以下为Link类中定义的成员 ————————————
    private Node root; // 保存根节点
    // ———————————— 以下为Link类中定义的方法 ————————————
    public void add(E e) {
        if (null == e) return; // 如增加的数据为null,则方法调用直接结束
        Node newNode = new Node(e);
        if (null == this.root) this.root = newNode; // 如没有根节点,则增加的节点即为根节点
        else this.root.addNode(newNode);
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        ILink<String> link = new LinkImpl<>();
        link.add("火车头");
        link.add("车厢一");
        link.add("车厢二");
        link.add("车厢三");
    }
}

课时135:获取集合个数

  • 获取集合个数:public int size()

  • 对链表的数据个数进行统计:增加数据统计,并且当增加或删除数据时都应该对个数进行修改。

  • 实现步骤:

    1. 在ILink接口里追加获取数据个数的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
      }
      
    2. 在LinkImpl子类里追加获取数据个数的方法:

      private int count; // 保存数据个数
      
    3. 在add()方法里进行数据个数的追加:

      public void add(E e) {
      	if (null == e) return; // 如增加的数据为null,则方法调用直接结束
          Node newNode = new Node(e);
          if (null == this.root) this.root = newNode; // 如没有根节点,则增加的节点即为根节点
          else this.root.addNode(newNode);
          this.count ++;
      }
      
    4. 在LinkImpl子类里返回数据的个数:

      public int size() {
          return this.count;
      }
      

(完整代码在 课时142)

课时136:空集合判断

  • 空集合判断:public boolean isEmpty()

  • 空集合:链表中尚未保存数据。

  • 实现步骤:

    1. 在ILink接口里追加判断空集合的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
      }
      
    2. 在LinkImpl子类里追加判断空集合的方法:

      public boolean isEmpty() {
      //        return null == this.root; // 方法一
              return 0 == this.count; // 方法二
      }
      

(完整代码在 课时142)

课时137:返回集合数据

链表数据返回.PNG

  • 实现步骤:

    1. 在ILink接口里追加返回集合数据的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
      }
      
    2. 在LinkImpl子类里追加两个属性:

      private int foot; // 操作数组的脚标
      private Object[] returnData; // 返回数据的保存
      
    3. 在Node类中递归获取数据:

      public void toArrayNode() {
      	LinkImpl.this.returnData[LinkImpl.this.foot ++] = this.data;
          // 如果还有下一个节点,则继续去获取
          if (null != this.next) this.next.toArrayNode();
      }
      
    4. 在LinkImpl子类里追加返回集合数据的方法:

      public Object[] toArray() {
      	if (this.isEmpty()) return null; // 空集合则直接返回null
          this.foot = 0; // 脚标清零
          this.returnData = new Object[this.count]; // 根据集合长度生成返回的数组
          this.root.toArrayNode(); // 利用Node类递归获取数据
          return this.returnData;
      }
      

(完整代码在 课时142)

课时138:根据索引取得数据

  • 获取指定索引的数据:public E get(int index)

链表根据索引获取数据.PNG

  • 实现步骤:

    1. 在ILink接口里追加根据索引获取数据的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
          public E get(int index); // 根据索引获取数据
      }
      
    2. 在Node类中递归查找节点:

      public E getNode(int index) {
      	if (index == LinkImpl.this.foot ++) return this.data; // 索引相同,返回数据
          else return this.next.getNode(index); // 索引不同,递归下一个节点
      }
      
    3. 在LinkImpl子类里追加根据索引获取数据的方法:

      public E get(int index) {
              if (this.count <= index) return null; // 如果索引超过集合长度则返回null
              this.foot = 0; // 脚标清零
              return this.root.getNode(index);
          }
      

(完整代码在 课时142)

  • 请注意!数组获取一个数据的时间复杂度为1,而链表获取一个数据的时间复杂度为n。

课时139:链表(修改指定索引数据)

  • 修改指定索引的数据:public void set(int index, E data)

  • 实现步骤:

    1. 在ILink接口里追加修改指定索引数据的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
          public E get(int index); // 根据索引获取数据
          public void set(int index, E data); // 修改指定索引的数据
      }
      
    2. 在Node类中递归查找节点并修改数据:

      public void setNode(int index, E data) {
      	if (index == LinkImpl.this.foot ++) this.data = data; // 索引相同,修改数据
          else this.next.setNode(index, data); // 索引不同,递归下一个节点
      }
      
    3. 在LinkImpl子类里追加修改指定索引数据的方法:

      public void set(int index, E data) {
      	if (this.count <= index) return; // 如果索引超过集合长度则方法调用直接结束
          this.foot = 0; // 脚标清零
          this.root.setNode(index, data);
      }
      

(完整代码在 课时142)

  • 同样,链表修改一个数据的时间复杂度也为n,因为依然需要进行数据的遍历。

课时140:链表(判断数据是否存在)

  • 判断数据是否存在:public boolean contains(E data)

  • 实现步骤:

    1. 在ILink接口里追加判断数据是否存在的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
          public E get(int index); // 根据索引获取数据
          public void set(int index, E data); // 修改指定索引的数据
          public boolean contains(E data); // 判断数据是否存在
      }
      
    2. 在Node类中递归判断数据:

      public boolean containsNode(E data) {
      	if (this.data.equals(data)) return true; // 找到该数据
          else {
          	if (null == this.next) return false; // 没有后续节点,表示找不到该数据
              else return this.next.containsNode(data); // 向后递归查找
          }
      }
      
    3. 在LinkImpl子类里追加判断数据是否存在的方法:

      public boolean contains(E data) {
      	if (null == data) return false; // 数据为null则直接返回false
          return this.root.containsNode(data); // 交给Node类判断
      }
      

(完整代码在 课时142)

课时141:链表(数据删除)

  • 数据删除:public void remove(E data)

  • 对于集合数据的删除,需要考虑的情况:

    • 要删除的是根节点:(LinkImpl类与根节点有关,所以这个判断由LinkImpl类完成)

      链表删除根节点.PNG

    • 要删除的不是根节点:(由Node类完成)

      链表删除非根节点.PNG

  • 实现步骤:

    1. 在ILink接口里追加数据删除的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
          public E get(int index); // 根据索引获取数据
          public void set(int index, E data); // 修改指定索引的数据
          public boolean contains(E data); // 判断数据是否存在
          public void remove(E data); // 删除数据
      }
      
    2. 在Node类中递归查找节点并删除数据:

      public void removeNode(Node previous, E data) {
          // 找到要删除的节点,将其前一个节点指向其后一个节点
          if (this.data.equals(data)) previous.next = this.next;
          // 下一个节点不为空,则递归下一个节点
          else if (null != this.next) this.next.removeNode(this, data);
      }
      
    3. 在LinkImpl子类里追加数据删除的方法:

      public void remove(E data) {
          if (this.contains(data)) { // 判断数据是否存在
          	// 删除的节点为根节点
          	if (this.root.data.equals(data)) this.root = this.root.next;
          	// 删除的节点不为根节点,交由Node类删除
              else this.root.next.removeNode(this.root, data);
              this.count --;
          }
      }
      

(完整代码在 课时142)

课时142:链表(清空链表)

  • 清空链表:public void clean()

  • 实现步骤:

    1. 在ILink接口里追加清空集合的方法:

      interface ILink<E> { // 设置泛型避免安全隐患
          public void add(E e); // 增加数据
          public int size(); // 获取数据的个数
          public boolean isEmpty(); // 判断是否为空集合
          public Object[] toArray(); // 将集合数据以数组的形式返回
          public E get(int index); // 根据索引获取数据
          public void set(int index, E data); // 修改指定索引的数据
          public boolean contains(E data); // 判断数据是否存在
          public void remove(E data); // 删除数据
          public void clean(); // 清空集合
      }
      
    2. 在LinkImpl子类里追加清空集合的方法:

      public void clean() {
          this.root = null; // 赋空根节点,后续的节点也就找不到了
          this.count = 0; // 个数清零
      }
      
  • 单向链表类的完整代码:

interface ILink<E> { // 设置泛型避免安全隐患
    public void add(E e); // 增加数据
    public int size(); // 获取数据的个数
    public boolean isEmpty(); // 判断是否为空集合
    public Object[] toArray(); // 将集合数据以数组的形式返回
    public E get(int index); // 根据索引获取数据
    public void set(int index, E data); // 修改指定索引的数据
    public boolean contains(E data); // 判断数据是否存在
    public void remove(E data); // 删除数据
    public void clean(); // 清空集合
}
class LinkImpl<E> implements ILink<E> {
    private class Node { // 保存节点
        private E data; // 保存数据
        private Node next; // 保存下一个节点
        public Node(E data) {
            this.data = data;
        }
        // 第一次调用:this = LinkImpl.root;
        // 第二次调用:this = LinkImpl.root.next;
        // 第三次调用:this = LinkImpl.root.next.next;
        public void addNode(Node newNode) { // 保存新节点
            // 当前节点的下一个节点为null,则保存当前节点为下一个节点
            if (null == this.next) this.next = newNode;
            // 当前节点的下一个节点不为null,则递归调用
            else this.next.addNode(newNode); //
        }
        // 第一次调用:this = LinkImpl.root;
        // 第二次调用:this = LinkImpl.root.next;
        // 第三次调用:this = LinkImpl.root.next.next;
        public void toArrayNode() {
            LinkImpl.this.returnData[LinkImpl.this.foot ++] = this.data;
            // 如果还有下一个节点,则继续去获取
            if (null != this.next) this.next.toArrayNode();
        }
        public E getNode(int index) {
            if (index == LinkImpl.this.foot ++) return this.data; // 索引相同,返回数据
            else return this.next.getNode(index); // 索引不同,递归下一个节点
        }
        public void setNode(int index, E data) {
            if (index == LinkImpl.this.foot ++) this.data = data; // 索引相同,修改数据
            else this.next.setNode(index, data); // 索引不同,递归下一个节点
        }
        public boolean containsNode(E data) {
            if (this.data.equals(data)) return true; // 找到该数据
            else {
                if (null == this.next) return false; // 没有后续节点,表示找不到该数据
                else return this.next.containsNode(data); // 向后递归查找
            }
        }
        public void removeNode(Node previous, E data) {
            // 找到要删除的节点,将其前一个节点指向其后一个节点
            if (this.data.equals(data)) previous.next = this.next;
            // 下一个节点不为空,则递归下一个节点
            else if (null != this.next) this.next.removeNode(this, data);
        }
    }
    // ———————————— 以下为Link类中定义的成员 ————————————
    private Node root; // 保存根节点
    private int count; // 保存数据个数
    private int foot; // 操作数组的脚标
    private Object[] returnData; // 返回数据的保存
    // ———————————— 以下为Link类中定义的方法 ————————————
    public void add(E e) {
        if (null == e) return; // 如增加的数据为null,则方法调用直接结束
        Node newNode = new Node(e);
        if (null == this.root) this.root = newNode; // 如没有根节点,则增加的节点即为根节点
        else this.root.addNode(newNode);
        this.count ++;
    }
    public int size() {
        return this.count;
    }
    public boolean isEmpty() {
//        return null == this.root; // 方法一
        return 0 == this.count; // 方法二
    }
    public Object[] toArray() {
        if (this.isEmpty()) return null; // 空集合则直接返回null
        this.foot = 0; // 脚标清零
        this.returnData = new Object[this.count]; // 根据集合长度生成返回的数组
        this.root.toArrayNode(); // 利用Node类递归获取数据
        return this.returnData;
    }
    public E get(int index) {
        if (this.count <= index) return null; // 如果索引超过集合长度则返回null
        this.foot = 0; // 脚标清零
        return this.root.getNode(index);
    }
    public void set(int index, E data) {
        if (this.count <= index) return; // 如果索引超过集合长度则方法调用直接结束
        this.foot = 0; // 脚标清零
        this.root.setNode(index, data);
    }
    public boolean contains(E data) {
        if (null == data) return false; // 数据为null则直接返回false
        return this.root.containsNode(data); // 交给Node类判断
    }
    public void remove(E data) {
        if (this.contains(data)) { // 判断数据是否存在
            if (this.root.data.equals(data)) this.root = this.root.next; // 删除的节点为根节点
            else this.root.next.removeNode(this.root, data); // 删除的节点不为根节点,交由Node类删除
            this.count --;
        }
    }
    public void clean() {
        this.root = null; // 赋空根节点,后续的节点也就找不到了
        this.count = 0; // 个数清零
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        ILink<String> link = new LinkImpl<>();
        System.out.println("【数据增加之前】数据个数:" + link.size() + ",是否为空集合:" + link.isEmpty());
        link.add("火车头");
        link.add("车厢一");
        link.add("车厢二");
        link.add("车厢三");
        System.out.println("【数据增加之后】数据个数:" + link.size() + ",是否为空集合:" + link.isEmpty());
        link.set(0, "车厢零");
        link.remove("车厢三");
        Object[] result = link.toArray();
        for (Object obj : result) {
            System.out.println(obj);
        }
        System.out.println("—————— 数据获取的分割线 ——————");
        System.out.println(link.get(0));
        System.out.println(link.get(2));
        System.out.println("—————— 数据判断的分割线 ——————");
        System.out.println(link.contains("车厢五"));
        System.out.println(link.contains("车厢一"));
        System.out.println("【数据清空之前】数据个数:" + link.size() + ",是否为空集合:" + link.isEmpty());
        link.clean();
        System.out.println("【数据清空之后】数据个数:" + link.size() + ",是否为空集合:" + link.isEmpty());
    }
}

课时143:综合实战:宠物商店

  • 假设有一个宠物商店,里面可以出售各种宠物,要求可以实现宠物的上架、下架处理,也可以根据关键字查询出宠物的信息。

链表案例-宠物商店.PNG

  • 实现步骤:

    1. 定义宠物的标准:
    2. 定义宠物商店类:
    3. 根据宠物的标准来定义具体宠物类;
  • 完整代码:

interface ILink<E> {} // 略,请参考课时142的代码
class LinkImpl<E> implements ILink<E> {} // 略,请参考课时142的代码


interface Pet { // 宠物标准
    public String getName(); // 获取名字
    public String getColor(); // 获取颜色
}
class PetShop { // 宠物商店类
    public ILink<Pet> pets = new LinkImpl<>();
    public void add(Pet pet) {
        this.pets.add(pet);
    }
    public void delete(Pet pet) {
        this.pets.remove(pet);
    }
    public Object[] all () {
        return this.pets.toArray();
    }
    public ILink<Pet> search(String keyword) {
        ILink<Pet> searchResult = new LinkImpl<>();
        Object[] result = this.pets.toArray();
        if (null != result) {
            for (Object obj : result) {
                if (obj instanceof Pet) {
                    Pet pet = (Pet) obj;
                    if (pet.getName().contains(keyword) || pet.getColor().contains(keyword))
                        searchResult.add(pet);
                }
            }
        }
        return searchResult;
    }
}
class Cat implements Pet {
    private String name;
    private String color;
    public Cat(String name, String color) {
        this.name = name;
        this.color = color;
    }
    public String getName() {
        return this.name;
    }
    public String getColor() {
        return this.color;
    }
    public String toString() {
        return "【宠物猫】名字:" + this.name + ",颜色:" + this.color;
    }
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (!(obj instanceof Cat)) return false;
        if (this == obj) return true;
        Cat cat = (Cat) obj;
        return this.name.equals(cat.getName()) && this.color.equals(cat.getColor());
    }
}
class Dog implements Pet {
    private String name;
    private String color;
    public Dog(String name, String color) {
        this.name = name;
        this.color = color;
    }
    public String getName() {
        return this.name;
    }
    public String getColor() {
        return this.color;
    }
    public String toString() {
        return "【宠物狗】名字:" + this.name + ",颜色:" + this.color;
    }
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (!(obj instanceof Dog)) return false;
        if (this == obj) return true;
        Dog dog = (Dog) obj;
        return this.name.equals(dog.getName()) && this.color.equals(dog.getColor());
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        PetShop shop = new PetShop(); // 开宠物商店
        Pet pet1 = new Dog("黄斑狗", "黄色");
        Pet pet2 = new Cat("小强猫", "绿色");
        Pet pet3 = new Cat("小蓝猫", "蓝色");
        Pet pet4 = new Cat("大黄猫", "黄色");
        Pet pet5 = new Dog("大黑狗", "黑色");
        shop.add(pet1); // 上架
        shop.add(pet2); // 上架
        shop.add(pet3); // 上架
        shop.add(pet4); // 上架
        shop.add(pet5); // 上架
        System.out.println("—————— 上架宠物 ——————");
        Object[] result = shop.all();
        for (Object obj : result) {
            System.out.println(obj);
        }
        System.out.println("—————— 查找宠物 ——————");
        result = shop.search("黄").toArray(); // 查找
        for (Object obj : result) {
            System.out.println(obj);
        }
        System.out.println("—————— 上架宠物 ——————");
        shop.delete(pet4); // 下架
        shop.delete(pet5); // 下架
        result = shop.all();
        for (Object obj : result) {
            System.out.println(obj);
        }
    }
}

课时144:综合实战:超市购物车

  • 小明去超市买东西,所有买到的东西都放在了购物车里,最后到收银台一起结账。

链表案例-超市购物车.PNG

  • 实现步骤:
    1. 定义商品的标准:
    2. 定义购物车标准;
    3. 定义购物车实现类
    4. 定义收银台类:
    5. 根据商品的标准来定义具体商品类;
  • 完整代码:
interface ILink<E> { // 设置泛型避免安全隐患
    public void add(E e); // 增加数据
    public int size(); // 获取数据的个数
    public boolean isEmpty(); // 判断是否为空集合
    public Object[] toArray(); // 将集合数据以数组的形式返回
    public E get(int index); // 根据索引获取数据
    public void set(int index, E data); // 修改指定索引的数据
    public boolean contains(E data); // 判断数据是否存在
    public void remove(E data); // 删除数据
    public void clean(); // 清空集合
}
class LinkImpl<E> implements ILink<E> {
    private class Node { // 保存节点
        private E data; // 保存数据
        private Node next; // 保存下一个节点
        public Node(E data) {
            this.data = data;
        }
        // 第一次调用:this = LinkImpl.root;
        // 第二次调用:this = LinkImpl.root.next;
        // 第三次调用:this = LinkImpl.root.next.next;
        public void addNode(Node newNode) { // 保存新节点
            // 当前节点的下一个节点为null,则保存当前节点为下一个节点
            if (null == this.next) this.next = newNode;
                // 当前节点的下一个节点不为null,则递归调用
            else this.next.addNode(newNode); //
        }
        // 第一次调用:this = LinkImpl.root;
        // 第二次调用:this = LinkImpl.root.next;
        // 第三次调用:this = LinkImpl.root.next.next;
        public void toArrayNode() {
            LinkImpl.this.returnData[LinkImpl.this.foot ++] = this.data;
            // 如果还有下一个节点,则继续去获取
            if (null != this.next) this.next.toArrayNode();
        }
        public E getNode(int index) {
            if (index == LinkImpl.this.foot ++) return this.data; // 索引相同,返回数据
            else return this.next.getNode(index); // 索引不同,递归下一个节点
        }
        public void setNode(int index, E data) {
            if (index == LinkImpl.this.foot ++) this.data = data; // 索引相同,修改数据
            else this.next.setNode(index, data); // 索引不同,递归下一个节点
        }
        public boolean containsNode(E data) {
            if (this.data.equals(data)) return true; // 找到该数据
            else {
                if (null == this.next) return false; // 没有后续节点,表示找不到该数据
                else return this.next.containsNode(data); // 向后递归查找
            }
        }
        public void removeNode(Node previous, E data) {
            // 找到要删除的节点,将其前一个节点指向其后一个节点
            if (this.data.equals(data)) previous.next = this.next;
                // 下一个节点不为空,则递归下一个节点
            else if (null != this.next) this.next.removeNode(this, data);
        }
    }
    // ———————————— 以下为Link类中定义的成员 ————————————
    private Node root; // 保存根节点
    private int count; // 保存数据个数
    private int foot; // 操作数组的脚标
    private Object[] returnData; // 返回数据的保存
    // ———————————— 以下为Link类中定义的方法 ————————————
    public void add(E e) {
        if (null == e) return; // 如增加的数据为null,则方法调用直接结束
        Node newNode = new Node(e);
        if (null == this.root) this.root = newNode; // 如没有根节点,则增加的节点即为根节点
        else this.root.addNode(newNode);
        this.count ++;
    }
    public int size() {
        return this.count;
    }
    public boolean isEmpty() {
//        return null == this.root; // 方法一
        return 0 == this.count; // 方法二
    }
    public Object[] toArray() {
        if (this.isEmpty()) return null; // 空集合则直接返回null
        this.foot = 0; // 脚标清零
        this.returnData = new Object[this.count]; // 根据集合长度生成返回的数组
        this.root.toArrayNode(); // 利用Node类递归获取数据
        return this.returnData;
    }
    public E get(int index) {
        if (this.count <= index) return null; // 如果索引超过集合长度则返回null
        this.foot = 0; // 脚标清零
        return this.root.getNode(index);
    }
    public void set(int index, E data) {
        if (this.count <= index) return; // 如果索引超过集合长度则方法调用直接结束
        this.foot = 0; // 脚标清零
        this.root.setNode(index, data);
    }
    public boolean contains(E data) {
        if (null == data) return false; // 数据为null则直接返回false
        return this.root.containsNode(data); // 交给Node类判断
    }
    public void remove(E data) {
        if (this.contains(data)) { // 判断数据是否存在
            if (this.root.data.equals(data)) this.root = this.root.next; // 删除的节点为根节点
            else this.root.next.removeNode(this.root, data); // 删除的节点不为根节点,交由Node类删除
            this.count --;
        }
    }
    public void clean() {
        this.root = null; // 赋空根节点,后续的节点也就找不到了
        this.count = 0; // 个数清零
    }
}


interface IGood { // 商品标准
    public String getName(); // 获取商品名称
    public double getPrice(); // 获取商品价格
}
interface IShopCar { // 购物车标准
    public void add(IGood good); // 添加商品
    public void delete(IGood good); // 删除商品
    public Object[] all(); // 获取所有商品
}
class ShopCarImpl implements IShopCar {
    private ILink<IGood> goods = new LinkImpl<>();
    public void add(IGood good) {
        this.goods.add(good);
    }
    public void delete(IGood good) {
        this.goods.remove(good);
    }
    public Object[] all() {
        return this.goods.toArray();
    }
}
class Cashier { // 收银台类
    private IShopCar shopCar;
    public Cashier(IShopCar shopCar) {
        this.shopCar = shopCar;
    }
    public double checkOut() { // 结账,计算总价
        double total = 0.0;
        Object[] result = this.shopCar.all();
        for (Object obj : result) {
            IGood good = (IGood) obj;
            total += good.getPrice();
        }
        return total;
    }
    public int getCount() { // 获取商品数量
        return this.shopCar.all().length;
    }
}
class Book implements IGood {
    private String name;
    private double price;
    public Book(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return this.name;
    }
    public double getPrice() {
        return this.price;
    }
    public String toString() {
        return "【图书】名称:" + this.name + ",价格:" + this.price;
    }
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (this == obj) return true;
        if (!(obj instanceof Book)) return false;
        Book book = (Book) obj;
        return this.name.equals(book.getName()) && this.price == book.getPrice();
    }
}
class Bag implements IGood {
    private String name;
    private double price;
    public Bag(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return this.name;
    }
    public double getPrice() {
        return this.price;
    }
    public String toString() {
        return "【背包】名称:" + this.name + ",价格:" + this.price;
    }
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (this == obj) return true;
        if (!(obj instanceof Bag)) return false;
        Bag bag = (Bag) obj;
        return this.name.equals(bag.getName()) && this.price == bag.getPrice();
    }
}
public class JavaDemo {
    public static void main(String[] args) {
        IShopCar shopCar = new ShopCarImpl();
        IGood good1 = new Book("Java开发", 79.8);
        IGood good2 = new Book("Oracle开发", 89.8);
        IGood good3 = new Bag("Nike背包", 889.8);
        shopCar.add(good1);
        shopCar.add(good2);
        shopCar.add(good3);
        Cashier cashier = new Cashier(shopCar);
        System.out.println("总计:" + cashier.checkOut() + ",购买件数:" + cashier.getCount());
        shopCar.delete(good3);
        System.out.println("总计:" + cashier.checkOut() + ",购买件数:" + cashier.getCount());
    }
}

————————————————————————————————————

思维导图

思维导图

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《Java面向对象编程(阿里云大学)》笔记(文档+思维导图) 的相关文章

  • 从txt文件中读取数据而不下载它?

    我想从提供的文本文件中解析信息 有没有一种方法可以在应用程序中执行此操作 而无需先下载文件 以某种方式传输文本内容 打开到 URL 的 Http 连接 使用内置 HttpURLConnection 或使用 commons httpclien
  • Java - 将无符号十六进制字符串解析为有符号长整型

    我有一堆十六进制字符串 其中之一是 d1bc4f7154ac9edb 这是 3333702275990511909 的十六进制值 如果执行 Long toHexString d1bc4f7154ac9edb 这与您得到的十六进制相同 现在
  • 来自行号的方法名称

    给定特定类源代码 Java C 的行号 是否有一种简单的方法来获取它所属的方法的名称 如果它落入其中 大概使用抽象语法树 这对于将 checkstyle 的输出限制为仅触及的方法很有用 我假设您必须使用抽象语法树来执行 Line gt Me
  • 从 Android 函数更新 Textview

    有人可以告诉我如何从函数更新 Android Textview 控件吗 我在互联网上进行了深入搜索 看到很多人都问同样的问题 我测试了线程但无法工作 有人有一个简单的工作示例吗 例如 调用一个函数 在循环中运行多次 并且该函数在 TextV
  • JAX-WS 入门 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有人可以推荐一些关于 JAX WS 入门的好教程吗 使用各种工具 如 wsgen 等 您可以从这里开始 通过 Java SE 6 平台介绍
  • Jenkins 未显示 Maven 编译器错误

    在 Jenkins 中构建多模块 maven 3 项目时 如果出现构建错误 我们会收到一条神秘消息 表明 Maven 编译器插件失败 这在上周才刚刚开始发生 INFO BUILD FAILURE INFO INFO Total time 1
  • 传递自定义类型查询参数

    如何接受自定义类型查询参数 public String detail QueryParam request final MYRequest request 上面的行在启动服务器时出现错误 jersey server model ModelV
  • Spring 从 JBoss 上下文加载 PropertySourcesPlaceholderConfigurer

    我有一个使用 PropertySourcesPlaceholderConfigurer 的 spring 3 1 应用程序加载设置 我想管理测试和生产环境 只需从服务器上下文加载设置覆盖本地文件属性中指定的设置 下一个示例在 Tomcat
  • 在 doxygen 中使用 @see 或 @link

    我之前用 Javadoc 记录并使用了标签 see link or see foo and link foo 在我的描述中链接到其他课程 现在我尝试了doxygen 似乎这些标签不兼容 如果我运行 doxygen 完整的标签将被简单地解释为
  • 递归取消 allOf CompletableFuture

    如果我有 CompletableFuture
  • java:为什么主线程等待子线程完成

    我有一个简单的java程序 主线程 main 创建并启动另一个线程t class T extends Thread Override public void run while true System out println Inside
  • JFrame 在连续运行代码时冻结

    我在使用时遇到问题JFrame 它会冻结 连续运行代码 下面是我的代码 点击时btnRun 我调用了该函数MainLoop ActionListener btnRun Click new ActionListener Override pu
  • 始终将双精度舍入

    我怎么总是能把一个double to an int 并且永远不要将其四舍五入 我知道Math round double 但我希望它始终向上舍入 所以如果是的话3 2 四舍五入为 4 您可以使用Math ceil method 请参阅Java
  • Java 8:如何创建毫秒、微秒或纳秒的 DateTimeFormatter?

    我需要创建格式化程序来解析具有可选的毫秒 微米或纳秒分数的时间戳 例如 对于我的需求 我看到以下机会 DateTimeFormatter formatter new DateTimeFormatterBuilder append DateT
  • Hybris:如何在impex中导入zip文件中的媒体?

    我知道我们可以导入未像这样压缩的图像 siteResource jar com project initialdata constants ProjectInitialDataConstants projectinitialdata imp
  • 线程数组?

    所以我在理解如何避免线程的顺序执行时遇到了问题 我试图创建一个线程数组并在单独的循环中执行 start 和 join 函数 这是我现在拥有的代码示例 private static int w static class wThreads im
  • 如何更改 JAX-WS Web 服务的地址位置

    我们目前已经公开了具有以下 URL 的 JAX RPC Web 服务 http xx xx xx xx myservice MYGatewaySoapHttpPort wsdl http xx xx xx xx myservice MYGa
  • 如何在不使用 -cp 开关的情况下在 Groovy 中自动加载数据库 jar?

    我想简化调用 Oracle 数据库的 Groovy 脚本的执行 如何将 ojdbc jar 添加到默认类路径以便我可以运行 groovy RunScript groovy 代替 groovy cp ojdbc5 jar RunScript
  • ebean 映射到 BYTEA 的数据类型是什么?

    我有一个游戏 2 0 2 需要在数据库中存储一些文件的应用程序 我们使用 Ebean 作为 ORM 我相信我的数据库中需要一个 BYTEA 列来存储该文件 但我不确定在我的模型中使用什么数据类型 我应该使用某种Blob 或者只是一个byte
  • 亚马逊 Linux - 安装 openjdk-debuginfo?

    我试图使用jstack在 ec2 实例上amazon linux 所以我安装了openjdk devel包裹 sudo yum install java 1 7 0 openjdk devel x86 64 但是 jstack 引发了异常j

随机推荐

  • Linux查看硬盘属性(机械硬盘/固态硬盘)

    通过命令lsblk d o name rota查看 xff0c 0表示固态硬盘 xff0c 1表示机械硬盘 xff0c sda为机械硬盘 xff0c sdb为固态硬盘
  • python计算众数scipy

    计算如下数组中每一行的众数 xff0c 期望结果 xff0c 第一行为1 xff0c 第二行为3 0 0 1 1 1 2 3 3 2 3 使用scipy 的统计包stats中的mode函数 xff0c 指定第二个维度 xff0c 返回元组
  • 【剑指offer】序列化二叉树

    解题思路 序列化时 xff0c 可以通过各种遍历方法 xff0c 例如层序遍历 递归遍历对二叉树进行遍历 xff0c 遍历过程中将每个节点的值拼接到字符串中 xff0c 注意每个节点之间用一个标识符隔开 xff0c 例如 xff0c 这是为
  • Linux cuda11.1安装torch_scatter,torch-sparse,torch-cluster,torch-spline-conv,torch-geometric

    创建虚拟环境 conda create n torch18 span class token assign left variable python span span class token operator 61 span span c
  • pytorch查看张量占用内存大小

    element size返回单个元素的字节大小 xff0c nelement返回元素个数 span class token keyword import span torch a span class token operator 61 s
  • MySQL8.0 集群安装 (K8S)

    尝试了很多版本的mysql镜像 xff0c 都存在这样那样的的问题 原始需求中 xff0c 需要同时支持x86 64 AMD64 和aarch64 ARM64V8 xff0c 最后找到Oracle官方出品的MySQL Server 8 0镜
  • 使用latex导出IEEE文献格式

    创建一个tex文件 xff0c 内容如下 documentclass span class token punctuation span a4paper span class token punctuation span 10pt span
  • IDEA中设置python解释器(不同虚拟环境)

    按住Ctrl 43 Shift 43 Alt 43 s xff0c 或 File gt Project Structure xff0c 在Module SDK处下拉找到对应的python解释器 xff0c 如果第一次添加python解释器
  • TF-IDF

    TF IDF xff08 term frequency inverse document frequency xff09 是一种用于信息检索与数据挖掘的常用加权技术 TF意思是词频 Term Frequency xff0c IDF意思是逆文
  • 第一次写博客-C/C++软件开发工程师需要学习哪些东西?

    学习路线概述 概述数据结构和算法操作系统计算机网络数据库设计模式 概述 作为一名本科机械电子 xff0c 研究生研究计算机视觉方向的211应届毕业生 xff0c 如何才能从事C C 43 43 软件开发类的工程师呢 xff1f 如果能有机会
  • Vue中使用v-for不能用index作为key值

    今天在改一个项目 xff0c 有一个 lt el tabs gt 的列表循环 xff0c 需要根据权限控制列表项的显示 xff0c 代码如下 xff1a span class token operator lt span template
  • java 冒泡排序 选择排序 插入排序及其异同点

    交换两坐标位置的swap 函数 之后要用到 span class token keyword public span span class token keyword static span span class token keyword
  • 自抗扰(ADRC)控制原理及控制器设计

    自抗扰控制是在PID控制算法基础上进行改进的新型控制方法 xff0c 它具有不依赖于控制对象模型 不区分系统内外扰的结构特点 常用的自抗扰控制器主要由跟踪微分器 xff08 Tracking Differentiator xff0c TD
  • LQR控制基本原理(包括Riccati方程具体推导过程)

    全状态反馈控制系统 状态反馈控制器 通过选择K xff0c 可以改变的特征值 xff0c 进而控制系统表现 LQR控制器 最优控制 xff0c 其本质就是让系统以某种最小的代价来让系统运行 xff0c 当这个代价被定义为二次泛函 xff0c
  • 运行VINS-Fusion时找不到vins_node节点的问题解决

    问题 xff1a 在执行 rosrun vins vins node span class token operator span span class token operator span catkin ws span class to
  • Faster RCNN(Pytorch版本)代码及理论笔记

    文章目录 前言一 Faster RCNN整体流程二 PASCAL VOC2012数据集1 简介2 下载方式3 文件结构及含义 三 加载数据集四 数据预处理1 流程2 标准化数据3 缩放4 将图片处理为统一尺寸5 数据预处理的输入输出 五 B
  • K8S 网络CNI

    1 简介 CNI 容器网络接口 Container Network Interface xff1a 由Google和Core OS主导制定的容器网络标准 xff0c 它仅仅是一个接口 xff0c 具体的功能由各个网络插件自己去实现 xff1
  • 二叉树-前-中-后序遍历

    二叉树 二叉树概念 xff1a 1 空树 2 非空 xff1a 根节点 xff0c 根节点的左子树 xff0c 根节点的右子树组成 注意 xff01 注意 xff01 时刻记得二叉树是根 xff0c 根的左子树 xff0c 根的右子树 xf
  • 变量的声明与定义&&内部函数与外部函数

    1 变量的声明与定义 对于函数 声明部分是对有关标识符 xff08 变量 函数 结构体 xff09 的属性进行声明 xff1b 函数的声明是函数的原型 xff0c 而函数的定义是对函数功能的定义 对被调函数的声明是放在主调函数的声明部分 x
  • 《Java面向对象编程(阿里云大学)》笔记(文档+思维导图)

    课程链接 xff1a https edu aliyun com course 1011 xff08 还是建议去看课程 xff0c 笔记仅供参考 由于文中的所有内容均为手敲 xff0c 并且有些代码并未验证 xff0c 因此如有错误 xff0