【狂神说Java】Java零基础学习视频通俗易懂

HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# li @ evpower in ~/humble/blog/source/_posts/java on git:master x [18:27:56]
$ cat Hello.java
public class Hello{
public static void main(String[] args){ System.out.print("Hello World!"); }
}

# li @ evpower in ~/humble/blog/source/_posts/java on git:master x [18:28:02]
$ javac Hello.java

# li @ evpower in ~/humble/blog/source/_posts/java on git:master x [18:28:12]
$ ls
Hello.class Hello.java

# li @ evpower in ~/humble/blog/source/_posts/java on git:master x [18:28:13]
$ java Hello
Hello World!%
# li @ evpower in ~/humble/blog/source/_posts/java on git:master x [18:28:18]
$

Java数据类型分类

基本数据类型primitive type

1
2
3
4
5
6
7
8
byte num1 = 20;
short num2 = 20;
int num4 = 20;
long num5 = 20L; //可以用L或l,但l容易跟1弄混
float num6 = 50.1F;
double num7 = 3.141592653;
char num3 = 'a';
boolean flag = true; //false

引用数据类型reference type(类,接口,数组)

1
String name = "Lion";

进制

1
2
3
0b10010010 //二进制(0b)
050 //八进制(0)
0x1A //十六进制(0x)

避免使用浮点数比较,建议用类(BigDecimal)

1
2
3
4
5
6
7
float f = 0.1F;
double d = 1.0/10;
if(f == d) //false

float d1 = 123123123123123123F;
float d2 = d1 + 1;
if(d1 == d2) //true

类型转换

1
2
3
4
5
6
7
8
//byte,short,char -> int -> long -> float -> double //低->高 (高->低:强制类型转换 低->高:自动类型转换)
int i = 128;
byte b = (byte)i; //-128 强制转换 内存溢出
double d = i; //128 自动转换
int j = (int)23.7; //23
int k = (int)-45.89f; //-45
int money = 10_0000_0000; //1000000000, JDK7新特性,可以用下划线分割数字
long total = money * 12; //int型溢出,改为 long total = (long)money * 12L;

注意:

  • 不能对boolean进行转换
  • 不能把对象类型转成不相干的类型(把猪转人-no,把男转女-yes)
  • 把高容量转低容量时,强制转换
  • 转换时可能存在内存溢出或精度问题

变量

  • 类变量:(static)初始默认值是null
  • 实例变量:从属于对象,若不初始化,默认值是 0/0.0
  • 局部变量:必须声明和初始化
1
2
3
4
5
6
7
8
public class Variable{
static int allClicks = 0; //类变量
String str="hello world"; //实例变量

public void method(){
int i=0; //局部变量
}
}

常量

1
2
3
4
5
final double PI=3.14; //常量名建议使用全大写

/* 修饰符关键字不分先后顺序 */
static final double PI=3.14;
final static double PI=3.14;

变量的命名规范

  • 类成员变量:首字母小写驼峰命名法
  • 局部变量:首字母小写驼峰命名法
  • 常量:大写字母和下划线
  • 类名:首字母大写驼峰命名法
  • 方法名:首字母小写驼峰命名法

运算符

  • 算数运算符+,-,*,/,%,++,--
  • 赋值运算符=
  • 关系运算符>,<,>=,<=,==,!=,instanceof
  • 逻辑运算符&&,||,!
  • 位运算符&,|,^,~,>>,<<,>>>
  • 条件运算符? :
  • 扩展赋值运算符+=,-=,*=,/=

IDEAtips:Ctrl+d是复制当前行到下一行

短路运算

1
2
int c=5;
boolean d = (c<4)&&(c++<4); //d是false,c是5

字符串连接符号+

1
2
3
4
int a=10;
int b=20;
""+a+b //"1020"
a+b+"" //"30"

包机制

是为了更好地组织类,并且区别类名的命名空间。常用公司域名倒置作为包名:com.baidu.www。package用来封装包,import用来导入包(import需要放置在package后面)

1
2
package pkg1[. pkg2[. pkg3... ]];
import package1[.package2...].(classname|*); //*是通配符,表示导入包下所有的类

IDEAtips:需要用外部包的变量或方法时报错忘记import,按Alt+Enter可查询包方便导入

JavaDoc

javadoc命令是用来生成自己API文档的

1
2
3
4
5
6
7
参数信息
@author 作者名
@version 版本号
@since 指明需要最早适用的jdk版本
@param 参数名
@return 返回值情况
@throws 异常抛出情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.humble.base;

/**
* @author humble
* @version 1.0
* @since 1.8
*/
public class Hello {
String name;

/**
* @param name
* @return
* @throws Exception
*/
public String test(String name) throws Exception{ return name; }
}

在idea里右键Hello的类,Show in Files,在当前目录打开命令行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:05]
$ ls
Hello.java

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:07]
$ javadoc -encoding UTF-8 -charset UTF-8 Hello.java
正在加载源文件Hello.java...
正在构造 Javadoc 信息...
标准 Doclet 版本 1.8.0_265
正在构建所有程序包和类的树...
正在生成./com/humble/base/Hello.html...
Hello.java:17: 警告 - @return 标记没有参数。
正在生成./com/humble/base/package-frame.html...
正在生成./com/humble/base/package-summary.html...
正在生成./com/humble/base/package-tree.html...
正在生成./constant-values.html...
正在构建所有程序包和类的索引...
正在生成./overview-tree.html...
正在生成./index-all.html...
正在生成./deprecated-list.html...
正在构建所有类的索引...
正在生成./allclasses-frame.html...
正在生成./allclasses-noframe.html...
正在生成./index.html...
正在生成./help-doc.html...
1 个警告

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:19:47]
$ ls
allclasses-frame.html Hello.java package-list
allclasses-noframe.html help-doc.html script.js
com index-all.html stylesheet.css
constant-values.html index.html
deprecated-list.html overview-tree.html

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [23:21:36]
$

用浏览器打开index.html即可

Scanner对象

用于获取用户输入

Demo01:使用next()

  • 1.一定要读取到有效字符后才可以结束输入
  • 2.对输入有效字符前遇到的空白,next()方法会自动将其去掉
  • 3.只有输入有效字符后才将其后面输入的空白作为分隔符或结束符
  • 4.next()不能得到带有空格的字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.humble.scanner;
import java.util.Scanner;

public class Demo01 {
public static void main(String[] args) {
//创建扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);

System.out.println("使用next方式接收:");

if(scanner.hasNext()){ //判断用户有没有输入字符串
//使用next方式接收
String str = scanner.next(); //程序会等待用户输入完毕
System.out.println("输入内容为:" + str);
}

//凡是属于IO流的类,如果不关闭会一直占用资源,要养成良好习惯,用完就关
scanner.close();
}
}

运行Demo01(只输出了空格前的单词,再调用next()可获得下一个单词)

1
2
3
4
5
使用next方式接收:
hello world!
输入内容为:hello

Process finished with exit code 0

Demo02:使用nextLine()

  • 1.以Enter为结束符,即返回的是回车之前的全部字符
  • 2.可以获得空白字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.humble.scanner;
import java.util.Scanner;

public class Demo02 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("使用nextline方式接收:");
if(scanner.hasNextLine()){
//使用nextline方式接收
String str = scanner.nextLine(); //程序会等待用户输入完毕
System.out.println("输入内容为:" + str);
}
scanner.close();
}
}

运行Demo02(输出整行)

1
2
3
4
5
使用nextline方式接收:
hello world!
输入内容为:hello world!

Process finished with exit code 0

反编译

Project Structure-Project Settings-Project-Project comcompiler output
打开对应目录,子目录里面会有.class文件,把class文件拷贝到项目目录,然后导入到idea即可看到源码

IDEAtips:输入100.for即可补全for循环100次

方法的重载

重载就是在一个类中,有相同的方法名称,但形参不同的方法。

重载规则:

  • 1.方法名称必须相同
  • 2.参数列表必须不同(个数不同、类型不同、参数排列不同等)
  • 3.方法的返回类型可以相同也可不同
  • 4.仅仅返回类型不同不足以成为方法的重载

实现理论:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。

命令行传参

1
2
3
4
5
6
7
8
9
package com.humble.base;

public class Hello {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("arg[" + "] is " + args[i]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:41]
$ ls
Hello.java

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:41]
$ javac Hello.java

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:19:47]
$ ls
Hello.class Hello.java

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src/com/humble/base [16:20:07]
$ cd ../../../

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:12]
$ ls
com

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:13]
$ java com.humble.base.Hello

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:28]
$ java com.humble.base.Hello this is argv
arg[] is this
arg[] is is
arg[] is argv

# li @ evpower in ~/IdeaProjects/StudyJAVA/Hello/src [16:20:38]
$

可变参数

  • JDK1.5开始,java支持:给一个方法传递同类型的可变参数
  • 在方法声明中,在指定参数类型后面加一个省略号(…)
  • 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。

递归

递归结构包括两个部分:

  • 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。
  • 递归体:什么时候需要调用自身方法。

数组

1
2
3
dataType[] arrayRefVar; //首选
dataType arrayRefVar[]; //只是为了方便C/C++程序员
dataType[] arrayRefVar = new dataType[arraySize];

三种初始化

1
2
3
4
5
6
7
8
9
10
//静态初始化
int[] a = {1,2,3};
Man[] mans = {new Man(1,1), new Man(2,2)};

//动态初始化
int[] a = new int[2];
a[0] = 1;
a[1] = 2;

//数组默认初始化:数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中每个元素也被按照实例变量同样的方式被隐式初始化。

Arrays类

数组本身没有什么方法提供调用,但API中提供了工具类java.util.Arrays对数据对象进行一些基本操作。Arrays类中的方法都是Static修饰的静态方法,使用时可以直接用类名调用,不需要使用对象来调用(想用对象也可以)。

  • 给数组赋值用fill方法
  • 对数组排序用sort方法,按升序
  • 比较数组用equals,比较元素值是否相等
  • 查找数组元素用binarySearch,对排好序的数组进行二分发查找

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static int[] sort(int[] a){
int tmp = 0;
boolean flag = false; //假设已经排好序,即无需位置交换

for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - 1 - i; j++) {
if(a[j+1] < a[j]){
tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true; //表示有位置交换
}
}
/* 确实没有发生位置交换,表示已经排好序 */
if(!flag){ break; }
}
return a;
}

稀疏数组

当数组中大部分元素为0,或者为同一值时,可以使用稀疏数组来保存。处理方式是:

  • 记录数组一共几行几列几个非0值
  • 把具有不同值的元素和行列及值记录在一个小规模数组中,从而压缩数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Hello {
public static void main(String[] args) {
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
for (int[] ints : array1) { //打印原数组
for (int anInt : ints) { System.out.print(anInt); }
System.out.println();
}

System.out.println("==开始转为稀疏数组==");
int sum = 0;
for (int[] ints : array1) { //统计非0总数
for (int anInt : ints) {
if(anInt != 0) { sum++; }
}
}
//总长、总宽、非0总数
int[][] array2 = new int[sum+1][3];
array2[0][0] = array1[0].length;
array2[0][1] = array1.length;
array2[0][2] = sum;

int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if(array1[i][j] != 0){
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
System.out.println("cow\trow\tvalue");
for (int[] ints : array2) { System.out.println(Arrays.toString(ints)); }

System.out.println("==开始还原数组==");
int[][] array3 = new int[array2[0][0]][array2[0][1]];
for (int i = 1; i < array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
for (int[] ints : array1) {
for (int anInt : ints) { System.out.print(anInt); }
System.out.println();
}
}
}

运行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
00000000000
00100000000
00020000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000
==开始转为稀疏数组==
cow row value
[11, 11, 2]
[1, 2, 1]
[2, 3, 2]
==开始还原数组==
00000000000
00100000000
00020000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000
00000000000

面向对象

  • 对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是具体到微观操作,仍然需要面向过程的思路取处理。
  • 面向对象编程(Object-Oriented Programming),本质是:以类的方式组织代码,以对象的方式组织/封装数据。
  • 三大特性:封装、继承、多态。
  • 从认识的角度考虑是先有对象后有类。对象是具体的事物;类是针对对象的抽象。
  • 从代码运行角度考虑是先有类后有对象。类是对象的模板。

static方法

  • static:方法在类定义时就已经加载,所以其它地方可以直接用类名调用方法。
  • static:方法在对象定义时才加载,所以其它地方必须先实例化才能用实例(对象)调用方法。
  • 定义类时,在static方法内调用自己的非static方法会失败;在非static方法内可以直接调用自己的非static方法。

值传递和引用传递

java里面调用方法都是值传递,不是引用传递

1
2
3
4
5
6
7
8
9
10
11
12
public class Hello {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name); //未定义所以打印 null
Hello.change(person); //传了对象(引用传递,址传递)
System.out.println(person.name); //打印出 world
}

public static void change(Person person){ person.name = "world"; }
}

class Person{ String name; }

类和对象

  • 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但不能代表某一个具体的事物。
  • 对象是抽象概念的具体实例。
  • 使用new关键字创建对象时,除了分配内存空间,还会给对象进行默认的初始化以及对类中构造器的调用。

构造器

  • 类中的构造器也称构造方法,是在进行创建对象的时候必须要调用的,目的在于初始化值。
  • 没有人为初始化则默认为:数字(0,0.0),char(u0000),boolean(false),引用(null)
  • 构造器有两个特点:1.必须和类的名字相同,2.必须没有返回类型,也不能写void。
1
2
3
4
5
6
7
8
9
10
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
//Person humble = new Person(); //Person()实际上是构造方法
Person humble = new Person("Humble");
System.out.println(humble.name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Filename: Person.java */
package com.oop.demo;

public class Person {
//即使留空,默认也会存在一个无参构造方法Person()

/*
String name;
//显式定义无参构造方法
public Person(){ this.name = "hello"; }
*/

String name;
/*
当底下定义了带参构造方法,那其它地方就无法使用无参构造方法来创建实例
如果想保留无参构造方法,只需显式定义一下无参构造方法即可
*/
public Person(){ }

//定义有参构造方法
public Person(String name){ this.name = name; }
}

IDEAtips:Alt+Insert-Constructor-选择属性或不选属性-OK即可光标处快速生成构造方法

创建对象内存分析

1
2
3
4
5
6
内存
├── 栈(stack)
└── 堆(heap)
   ├── new分配区
   └── 方法区
      └── 静态方法区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Hello {
public static void main(String[] args) {
//new会在堆的new分配区申请一片内存并返回地址
//person变量保存在栈,它的值是一个指向堆的地址
Person person = new Person();
System.out.println(person.name); //未定义所以打印 null
Hello.change(person); //传了对象(引用传递,址传递)
System.out.println(person.name); //打印出 world
}

public static void change(Person person){
//person虽然是值传递,但传的却是地址值(引用值)
//地址指向的是堆内new分配区的对象空间
person.name = "world";
}
}

class Person{ String name; }

封装

  • 程序设计应该“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用。
  • 信息隐藏:禁止别人直接访问对象中的数据,而应该通过操作接口来访问。
  • 属性私有:get()或set()

封装的好处:

  1. 提高程序的安全性,保护数据
  2. 隐藏代码实现细节
  3. 统一接口
  4. 增加系统可维护性

IDEAtips:Alt+Insert-Getter and Setter-选择属性或不选属性-OK即可光标处快速生成get()set()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.oop.demo;

public class Student {
private String name;
private int age;
private int id;

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public int getAge() { return age; }
public void setAge(int age) { //用set修改私有属性可以保护数据合法性
if(age > 120 || age < 0){ this.age = 3; }
else { this.age = age; }
}

public int getId() { return id; }
public void setId(int id) { this.id = id; }
}

继承

  • 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
  • extends意思是扩展。子类是父类的扩展。
  • java中类只有单继承,没有多继承。
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。子类和父类之间,从意义上讲应该具有”is a”的关系。
  • 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
1
2
3
4
public class Student extends Person { } //Student继承Person
public class Student { //Student和person是组合关系
Person person;
}

继承例程

1
2
3
4
5
6
7
8
9
10
11
12
/* Filename: Person.java */
package com.oop.demo;

public class Person {
//public:可以被子类继承
//protected: 可被子继承
//default:(不加修饰就是这个)
//private:不可被子类继承(提供public get()/set())

protected int money = 10_0000_0000;
public void say() { System.out.println("I am a Person"); }
}
1
2
package com.oop.demo;
public class Student extends Person { }
1
2
3
4
5
6
7
8
9
10
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(s1.money); //1000000000
s1.say(); //I am a Person
}
}

IDEAtips:Ctrl+h可以打开继承树

java中所有的类都默认直接或间接继承Object类

super

  • super()调用父类的构造方法,必须在构造方法的第一行
  • super只能出现在’子类’的方法或构造方法中
  • super和this不能同时调用构造方法

用super调用父类

1
2
3
4
5
6
7
/* Filename: Person.java */
package com.oop.demo;

public class Person { //如果修饰改为private,那么子类就无法用super直接调用
protected String name = "person1";
public void print(){ System.out.println("Person.print"); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Filename: Student.java */
package com.oop.demo;

public class Student extends Person {
private String name = "student1";

public void test(String name){
System.out.println(name); //${name}
System.out.println(this.name); //student1
System.out.println(super.name); //person1
}

public void print(){ System.out.println("Student.print"); }
public void test1(){
print(); //Student.print
this.print();//Student.print
super.print();//Person.print
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
Student s1 = new Student();
s1.test("hello");
s1.test1();
}
}

/*运行输出
hello
student1
person1
Student.print
Student.print
Person.print
*/

super构造器

1
2
3
4
5
6
7
/* Filename: Person.java */
package com.oop.demo;

public class Person {
public Person(){ System.out.println("Person.Person"); }
public Person(String name){ System.out.println("Person.Person:"+name); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Filename: Student.java */
package com.oop.demo;

public class Student extends Person {
public Student() {
//隐藏调用,默认会先调用父类构造器super()。
//如果想显式调用super()则必须放在子类构造器第一行。
//若父类构造器有参,那么调用super()要带参,除非父类构造器重载
//super();
super("hello");
System.out.println("Student.Student");
}
}
1
2
3
4
5
6
7
8
9
10
11
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) { Student s1 = new Student(); }
}

/*运行输出
Person.Person:hello
Student.Student
*/

方法重写

为什么需要方法重写?(父类的功能,子类不一定需要或不一定满足)

方法重写注意:

    1. 需要继承关系,子类重写父类的方法;
    1. 只针对方法,不能是属性,并且是‘对象的方法(即非static修饰的)’,不是‘类的方法’,;
    1. 方法名必须相同;
    1. 返回值与参数列表及类型必须相同(否则就是重载了);
    1. 修饰符:范围可以扩大但不能缩小(即父用public修饰那么子重写时就不能用protected或更小的来修饰),public>protected>default>private;正常情况不会是private,因为这个方法不能用对象去调用,所以只要不是private都可以被子重写。
    1. 抛出的异常:范围可以被缩小但不能扩大;ClassNotFoundException(小)–>Exception(大)

例子1:带static方法(类的方法)该例子不是方法重写

1
2
3
4
5
6
/* Filename: B.java */
package com.oop.demo;

public class B {
public static void test(){ System.out.println("B.test"); }
}
1
2
3
4
5
6
/* Filename: A.java */
package com.oop.demo;

public class A extends B {
public static void test(){ System.out.println("A.test"); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
//类里面的static方法应该用类去调用,这里用对象去调用是为了测试
A a = new A();
a.test(); //最终调用类的方法

//父类的引用指向子类
B b = new A();
b.test(); //最终调用类的方法
}
}

/*运行输出如下,说明方法的调用只和定义的数据类型有关,跟new A()无关
A.test
B.test
*/

例子2:不带static方法(对象的方法)该例子是方法重写

1
2
3
4
5
6
/* Filename: B.java */
package com.oop.demo;

public class B {
public /*static*/ void test(){ System.out.println("B.test"); }
}
1
2
3
4
5
6
7
8
9
10
/* Filename: A.java */
package com.oop.demo;

public class A extends B {
@Override //@表示注解,是有功能的注释。Override表示重写
public void test() {
//super.test(); //默认是调用父类的方法,现在重写
System.out.println("A.test");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
A a = new A();
a.test(); //最终调用对象的方法

B b = new A();
b.test(); //最终调用对象的方法
}
}

/*运行输出如下,只要是new A()就是调用重写后的方法
A.test
A.test
*/

IDEAtips:Alt+InsertOverride Methods

类的方法优先级高于对象的方法。

  • 例子1里面,static修饰的是类方法。b.test()调用的是B类test(),(堆里面静态方法区)
  • 例子2里面,无static修饰是对象方法。b.test()调用的是对象btest(),但对象bA类new出来的,(堆里面new分配区)

重载与重写区别:

  • 重载是同一类里,方法名相同,参数个数或类型不同。
  • 重写是子父类间,子类重写父类的方法,方法名及参数一模一样,方法体不一样。并且不能是类方法,即不能用static修饰。

多态

  • 同一个方法根据调用对象的不同而采取不同的行为方式。
  • 其中调用对象的类型需要在执行过程中才能决定。
  • 一个对象的实际类型是确定的(堆的new分配区),但是指向它的引用(栈)可以是其他类型(父类或关系类)

注意事项:

  • 1.多态是针对方法的,不能是属性。
  • 2.必须有父子关系(继承),否则引用不能转换,比如String s4 = new Student(); 转换异常ClassCastException
  • 3.存在条件:必须父子关系(继承);方法需要重写(只有运行时才知道调用父方法还是子重写的方法);父类引用指向子类;

不能被子重写的方法:

  • 1.static方法,属于类,不属于实例
  • 2.final常量,
  • 3.private方法(因为不能继承)
1
2
3
4
/* Filename: Person.java */
package com.oop.demo;

public class Person { public void run(){ System.out.println("Person.run"); } }
1
2
3
4
5
6
7
8
9
/* Filename: Student.java */
package com.oop.demo;

public class Student extends Person {
@Override
public void run() { System.out.println("Student.run"); }

public void eat(){ System.out.println("Student.eat"); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
//String s4 = new Student(); //报错,引用只能是同类或父类

//如果Student没有Override run方法,那两个都是Person.run
//如果Student有Override run方法,那两个都是Student.run
s1.run();
s2.run();
//s3.run(); //报错,Object没有run方法

s1.eat();
//s2.eat(); //报错,因为父(Person)没有子(Student)的方法(eat)所以不能调,可以先强转为Student再调用

/**
* 所以,左边定义的类里有什么方法就只能调这些方法(自己的或继承的)
* 如果这些方法已被子类重写那调用就是重写后的方法
*/
}
}

instanceof

X instanceof Y,Y 必须是 引用X所指向的实例 的父类/同类才为真

1
2
3
/* Filename: Person.java */
package com.oop.demo;
public class Person { public void run(){ System.out.println("Person.run"); } }
1
2
3
/* Filename: Student.java */
package com.oop.demo;
public class Student extends Person { }
1
2
3
/* Filename: Teacher.java */
package com.oop.demo;
public class Teacher extends Person { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
Object o = new Student();
System.out.println(o instanceof Student); //true
System.out.println(o instanceof Person); //true
System.out.println(o instanceof Object); //true
System.out.println(o instanceof Teacher); //false
System.out.println(o instanceof String); //false
System.out.println("=====");
Person p = new Student();
System.out.println(p instanceof Student); //true(说Person是Student不成立,但引用p指向的实例是Student)
System.out.println(p instanceof Person); //true
System.out.println(p instanceof Object); //true
System.out.println(p instanceof Teacher); //false
//System.out.println(p instanceof String); //编译报错
System.out.println("=====");
Student s = new Student();
System.out.println(s instanceof Student); //true
System.out.println(s instanceof Person); //true
System.out.println(s instanceof Object); //true
//System.out.println(s instanceof Teacher); //编译报错
//System.out.println(s instanceof String); //编译报错
}
}

类的转换

  • 1.类的转换是发生在栈上,也就是转换引用而已
  • 2.把子类‘直接’转为父类,父类的引用(栈)指向子类的对象(堆),但是会丢失子类的方法
  • 3.把父类‘强制’转为子类,子类的引用(栈)指向父类的对象(堆),编译报错或运行报错,因为子类引用(栈)里有些方法在父类对象(堆)内找不到
  • 4.类的转换方便方法的调用,减少重复的代码
1
2
3
/* Filename: Person.java */
package com.oop.demo;
public class Person { public void run(){ System.out.println("Person.run"); } }
1
2
3
/* Filename: Student.java */
package com.oop.demo;
public class Student extends Person { public void go(){ System.out.println("Student.go"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Filename: Application.java */
package com.oop.demo;

public class Application {
public static void main(String[] args) {
Person p = new Person();
//Student conversion2s1 = p; //编译报错,因为堆内的对象为Person(父),不能用栈内Student(子)引用去指向它,需要强转
//Student conversion2s2 = (Student) p; //编译通过但运行报错,原因同上

Student s = new Student();
Person conversion2p = s; //子类转父类
//conversion2p.go(); //编译报错,因为栈内引用Person没有go方法,也就是丢失了Student(子)的方法go
Student reset2s = (Student)conversion2p;
reset2s.go(); //打印Student.go,证明转为父类会丢失子类方法go,再转回子类又恢复了方法go
}
}

static

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Filename:Student.java */
package com.oop.demo;

public class Student {
private static int age;
private double score;

public static void main(String[] args) {
Student s1 = new Student();

Student.age = 10; //类成员
s1.age = 20; //此处访问的也是类成员
System.out.println(s1.age); //20
System.out.println(Student.age); //20

//Student.score = 10; //编译报错,类被加载时不会分配非staitc的属性,非static属性是属于对象的
s1.score = 20;
}
}

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Filename:Student.java */
package com.oop.demo;
public class Student {
public void run(){
go(); //对象方法内可以调用类的方法
}
public static void go(){
//run(); //编译报错:类的方法内不可以调用对象的方法
}
public static void main(String[] args) {
//run(); //编译报错:类的方法内不可以调用对象的方法
go(); //类可以直接调用类自己的方法
}
}

static代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Filename:Person.java */
package com.oop.demo;

public class Person {
{ //2 :可以在里面赋初值
System.out.println("匿名代码块");
}

static{ //1 静态代码块只执行/初始化一次
System.out.println("静态代码块");
}

public Person() { //3
System.out.println("构造方法");
}

public static void main(String[] args) {
Person p1 = new Person();
System.out.println("====");
Person p2 = new Person();
/**运行输出
* 静态代码块
* 匿名代码块
* 构造方法
* ====
* 匿名代码块
* 构造方法
*/
}
}

外部package的函数导入

1
2
3
4
5
6
7
8
9
10
11
/* Filename:Test.java */
package com.oop.demo;
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
System.out.println(Math.random()); //用package去调用random
System.out.println(random()); //也可以先import static java.lang.Math.random之后就可以直接调用
System.out.println(PI);
}
}

final类

final修饰的类是没有子类的。

抽象类

  • abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么就是抽象方法;修饰类那就是抽象类
  • 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
  • 抽象类不能用new关键字来创建对象,它是用来让子类继承的。
  • 抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
  • 子类继承抽象类,那么就必须要实现抽象类里没有实现的抽象方法,否则该子类也要声明为抽象类。
1
2
3
4
5
6
7
8
9
10
11
/* Filename:Action.java */
package com.oop.demo;

//抽象(abstract)类,不能被new出来,只能被继承
public abstract class Action {
//抽象(abstract)方法如果:
// 被"abstract修饰的子类"继承可以不写方法体;
// 被"非abstract修饰的子类"继承需要写方法体。
public abstract void doSomething(); //抽象方法目的在于约束子类必须实现
public void go(){ System.out.println("Action.go"); }
}
1
2
3
4
5
6
7
8
9
/* Filename:A.java */
package com.oop.demo;
public abstract class A extends Action {
//因为doSomething()是抽象方法,所以子类(A)必须重写/实现出来,如果子类(A)是抽象类则不用重写/实现
//@Override
//public void doSomething() { }

//Action.go()不是抽象方法,所以子类(A)可以重写/实现,也可以不重写/实现
}

javaTips:类是单继承,接口可以多继承

思考题:1.抽象类不能new,那它有构造器吗?2.抽象类存在意义是什么?(游戏角色抽象出来,让子类继承去实现。提高开发效率,可扩展性高)

接口

  • 普通类:只有具体实现

  • 抽象类:具体实现和规范(抽象方法)都有

  • 接口:只有规范(使得约束和实现分离,项目组长约定好接口,员工面向接口编程)

  • 接口就是规范,定义是一组规则,体现了现实世界中“如果你是…则必须能…”的意思。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。

  • 接口本质是契约,就像我们人间的法律一样。制定好后大家都遵守。

  • OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(如C+++,java,C#等),就是因为设计模式所研究的,实际上就是如何合理去抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Filename:UserService.java */
package com.humble.oop;
public interface UserService {
//接口内的方法默认是public abstract,可省略不写
///*public abstract*/ void run(String name);
void add(String name);
void delete(String name);
void update(String name);
void query(String name);

//接口内的属性默认是常量 public static final(一般不这样定义使用)
/*public static final*/ int AGE=99;
}
1
2
3
/* Filename:TimeService.java */
package com.humble.oop;
public interface TimeService { void timer(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Filename:UserServiceImp.java */
package com.humble.oop;

//接口都需要有实现类,它不能直接被实例化,因为没构造方法
//如果类 implements 了接口,则必须重写里面所有的方法
//类是多继承(extends)的,接口是多实现(implements)的,或者说,类可以集成实现多个接口(interface)
public class UserServiceImp implements UserService,TimeService{
@Override
public void add(String name) { }

@Override
public void delete(String name) { }

@Override
public void update(String name) { }

@Override
public void query(String name) { }

@Override
public void timer() { }
}

内部类

内部类就是在一个类内部再定义一个类。比如A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。

  • 1.成员内部类
  • 2.静态内部类
  • 3.局部内部类
  • 4.匿名内部类

成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Filename:Outer.java */
package com.humble.oop;
public class Outer {
private int id = 10;
public void out(){ System.out.println("这是外部方法"); }

public class Inner { //成员内部类
public void in(){ System.out.println("这是内部方法"); }

//直接访问外部类的私有属性
public void getID(){ System.out.println("id = " + id); }
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/* Filename:Application.java */
package com.humble.oop;

public class Application {
public static void main(String[] args) {
Outer outer = new Outer();
//通过外部类的'实例'去new 内部类
Outer.Inner inner = outer.new Inner();
inner.in(); //这是内部方法
inner.getID(); //id = 10
}
}

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Filename:Outer.java */
package com.humble.oop;
public class Outer {
private int id = 10;
public void out(){ System.out.println("这是外部方法"); }

public static class Inner { //静态内部类
public void in(){ System.out.println("这是内部方法"); }

//编译报错:Inner在静态区,id属性不在静态区,除非用static修饰id,否则必须实例化才能访问id
//public void getID(){ System.out.println("id = " + id); }
}
}
1
2
3
4
5
6
7
8
9
10
11
/* Filename:Application.java */
package com.humble.oop;

public class Application {
public static void main(String[] args) {
Outer outer = new Outer();
//直接new 外部类里的内部类
Outer.Inner inner = new Outer.Inner();
inner.in(); //这是内部方法
}
}

注意:

1
2
3
4
5
6
7
8
9
10
11
/* Filename:outer.java */
package com.humble.oop;

public class Outer { }

//一个java文件中可以有多个class,但只有一个public class
class A{
public static void main(String[] args) {
System.out.println("A.main");
}
}

局部内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Filename:outer.java */
package com.humble.oop;

public class Outer {
public void method(){
//局部内部类
class Inner {
public void in(){
System.out.println("Inner.in");
}
}
}
}

匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* Filename:Test.java */
package com.humble.oop;

public class Test {
public static void main(String[] args) {
//不需要变量保存实例,没有名字初始化类(匿名)
new Apple().eat(); //Apple.eat

//也是匿名类
UserService userService = new UserService() {
@Override //运行的时候才重写/实现接口的方法,也就等于定义了新的类,并且用new实例化了,保存在userService中
public void hello() {
System.out.println("Test.hello");
}
};
userService.hello();//Test.hello
}
}

class Apple {
public void eat(){ System.out.println("Apple.eat"); }
}

interface UserService{
void hello();
}

异常

  • 运行过程中出现状况:用户输入不合法;打开一个文件可是文件不存在或格式不合法;读数据库却是空的;内存耗光或硬盘满了……
  • 软件程序在运行过程中,非常可能遇到刚刚提到的这些问题,叫做异常(Exception),翻译是例外,可以理解为例外的情况就是异常情况。
1
2
3
4
5
6
7
/* Filename:Application.java */
package com.humble.oop;
public class Application {
public static void main(String[] args) {
System.out.println(11/0); //Exception in thread "main" java.lang.ArithmeticException: / by zero
}
}

3种类型的异常:

  • 1.检查性异常,最具代表的检查性异常是用户错误输入引起的异常,这是开发者无法预见的。如:打开一个不存在的文件就会引发异常。这个异常在编译时不能被简单地忽略。
  • 2.运行时异常,最容易被避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 3.错误,错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如:当栈溢出的错误没办法在编译时就检查到。

Java把异常当作对象处理,并定义一个基类java.lang.Throwable作为所有异常的超类。在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

Error

  • Error类对象由Java虚拟机生成并抛出,大多数错误跟代码编写者所写的无关。
  • Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
  • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且大多数是程序运行时不允许出现这样的情况。

Exception

Exception分支中有一个重要的自类RuntimeException(运行时异常)

  • ArrayIndexOutOfBoundsException(数组下标越界)
  • NullPointerException(空指针异常)
  • ArithmeticException(算术异常)
  • MissingResourceException(丢失资源)
  • ClassNotFoundException(找不到类)

  • 这些是不检查异常,程序中可以选择捕获处理,也可不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

ErrorException的区别:

  • Error通常是灾难性、致命性的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程。
  • Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常

捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Filename:Test.java */
package com.humble.oop;

public class Test {
public static void main(String[] args) {
int a = 1;
int b = 0;

//try catch 需要有,finally可以没有。finally一般是需要在异常后关闭IO、释放资源的情况写
try{ //try监控区域
System.out.println(a / b);
}catch (ArithmeticException e){ //catch 捕获到异常后的处理区
System.out.println("捕获异常,除数不能为0");
}finally { //处理善后区,不管有无捕获异常都会处理,
System.out.println("finally");
}
}
}

捕获多个异常需要从小到大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Filename:Test.java */
package com.humble.oop;

public class Test {
public static void main(String[] args) {
int a = 1;
int b = 0;

try{
System.out.println(a / b);
}
catch (Error e){
System.out.println("Error");
}
catch (Exception e){
System.out.println("Exception");
}
catch (Throwable e){
System.out.println("Throwable");
}
finally {
System.out.println("finally");
}
}
}

IDEAtips:选中代码,按Ctrl+Alt+t可弹出补全逻辑

抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Filename:Test2.java */
package com.humble.oop;

public class Test2 {
public static void main(String[] args) {
try {
new Test2().test(1,0);
} catch (ArithmeticException e) {
e.printStackTrace();
}
}

//注意 throw 和 throws 两关键字
public void test(int a, int b) throws ArithmeticException{
if (b == 0) {
throw new ArithmeticException(); //throw 一般是在方法内处理不了,于是主动往更高级抛出异常
}
System.out.println(a / b);
}
}

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常(只需继承Exception类即可)

自定义异常类步骤:

  • 1.创建自定义异常类
  • 2.在方法中通过throw关键字抛出异常对象
  • 3.如果在当前抛出异常的方法中处理异常,则可使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
  • 4.在出现异常方法的调用者中捕获并处理异常

IDEAtips:双击Shift可弹出搜索框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Filename:MyException.java */
package com.humble.oop;

//自定义异常类
public class MyException extends Exception{
//传递数字>10
private int detail;

public MyException(int detail) {
this.detail = detail;
}

//toString:异常打印信息
@Override
public String toString() {
return "MyException{" + "detail=" + detail + '}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Filename:Test.java */
package com.humble.oop;

public class Test {
static void test(int a) throws MyException {
System.out.println("a = " + a);
if (a > 10){
throw new MyException(a);//抛出
}
System.out.println("Test.test");
}

public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
System.out.println("e = " + e);
}
}
}

/** 运行输出
* a = 11
* e = MyException{detail=11}
*/
  • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
  • 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  • 对于不确定的代码,可以加上try-catch,处理潜在的异常
  • 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
  • 具体如何处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加finally语句块去释放占用的资源