JavaSE 进阶 (1) 继承

继承概述

/images/java/JavaSE 进阶 (1) 继承/1.png
(图1)
/images/java/JavaSE 进阶 (1) 继承/2.png
(图2)
  • 继承是面向对象三大特征之一。(封装,继承,多态)
  • 可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。

继承的格式:

  • 格式:public class 子类名 extends 父类名{ }
  • 范例:public class Zi extends Fu{ }
  • Fu:是父类,也被称为基类、超类
  • Zi: 是子类,也被称为派生类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Fu {
	public void show() {
		System.out.println("show方法被调用了");
	}
}

public class Zi extends Fu {
	public void method() {
		System.out.println("method方法被调用了");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Fu f = new Fu();
		f.show();
		
		Zi z = new Zi();
		z.method();
		z.show();
}

继承中子类的特点:

(1)子类可以有父类的内容

(2)子类还可以有自己特有的内容

(3)子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如:父类私有的属性和方法)

/images/java/JavaSE 进阶 (1) 继承/3.png
(图3)

(4)Java 只支持单继承,不支持多继承,但支持多层继承,接口支持多继承。

  • 多继承会引起混乱,使得继承链过于复杂,系统难于维护。
  • 如果是多继承,每个类都有多个父类,那么就乱套了,成网状结构,虽然功能很强大。

维承的好处和弊端

继承好处:

  • 提高了代码 复用性 (多个类相同的成员可以放到同一个类中)
  • 提高了代码的 维护性 (如果方法的代码需要修改,修改一处即可)
  • 方便建模

继承弊端

  • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时,子类实现也不得不跟着变化,削弱了子类的独立性。

什么时候使用继承?

  • 继承体现的是:is a 关系 (什么是什么的一种)
  • 假设法:我有两个类 AB,如果他们满足 AB 的一种,或者 BA 的一种,就说明他们存在继承关系,这个时候就可以考虑使用继承来体现,否则就不能滥用继承
  • 举例:苹果和水果,猫和动物,猫和狗(不存在继承关系)
/images/java/JavaSE 进阶 (1) 继承/4.png
(图4)

继承的实现

 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
class Person {
	String name;
	int height;
	
	public void rest() {
		System.out println("休息一会!");
	}
}

class Student extends Person {
	String major; //专业
	
	public void study(){
		System.out.println("学习java");
	}
	
	public Student(String name, int height, String major) {
		//天然拥有父类的属性
		this.name = name;
		this.height = height;
		this.mafor = major;
	}
}

public class Test {
	public static void main(String[] args) {
		Student s = new Student("张三", 172, "Java");
		s.rest();
		s.study();
	}
}

Student 类继承 Person 类,Person 类中有的属性和方法 Student 类中也有,效果相当于把 Person 类中的代码复制到 Student 类中,这样就实现了代码的复用。

如果什么都不写,Person 默认继承自 Object 类,Person extends Object

Object 是老祖宗,Object 称为 person 的直接父类,personObject 的直接子类,所有类都是 Object 的子类,差别在于是不是直接子类,直接父类,默认情况下说的父类就是直接父类。

/images/java/JavaSE 进阶 (1) 继承/5.png
(图5)

继承中成员变量的访问特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fu {
	//年龄
	public int age = 40;
}

public class Zi extends Fu {
	//身高
	public int height = 175;

	public void show() {
		System.out.println(age);
		System.out.println(height);
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.show();  //40,175
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Fu{
	//年龄
	public int age = 40;
}

public class Zi extends Fu {
	//身高
	public int height = 175;
	//年龄
	public int age = 20;

	public void show() {
		System.out.println(age);
		System.out.println(height);
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.show();  //20,175
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Fu{
	//年龄
	public int age = 40;
}

public class Zi extends Fu {
	//身高
	public int height = 175;
	//年龄
	public int age = 20;

	public void show() {
		int age = 30;
		System.out.println(age);
		System.out.println(height);
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.show();  //30,175
}

在子类方法中访问一个变量

  • 子类局部范围找
  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错(不考虑父亲的父亲…)

注意:

  • 如果 子父类 中,出现了 重名的成员变量,通过 就近原则,会 优先使用子类
  • 如果一定要使用父类的,可以通过 super 关键字,进行区分。

super 关键字

 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
public class Fu {
	public int age = 40;
}

public class zi extends Fu {
	public int age = 20;
	
	public void show() {
		int age = 30;
		System.out.println(age);
		//我要访问本类的成员变量age,怎么办呢?
		System.out.println(this.age);
		//我要访问父类的成员变量age,怎么办呢?
		System.out.println(super.age);
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.show();
		/*
			30
			20
			40
		*/
}

super 关键字的用法和 this 关键字的用法相似

  • this:代表本类对象的引用
  • super:代表父类存储空间的标识 (可以理解为父类对象引用)

只要是父类私有的内容,在子类中,无法通过 super 关键字访问;

/images/java/JavaSE 进阶 (1) 继承/6.png
(图6)

Super:代表父类存储空间的标识, “可以看做” 是直接父类对象的引用。可以通过 super 来访问父类中被子类覆盖的方法或属性。

使用 super 可以调用父类的普通方法,语句没有位置限制,可以在子类中随便调用。

注意super 在子类里边写的时候,代表对直接父类的引用,super 只用于子类里边

继承中构造方法的访问特点

 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
public class Fu {
	public Fu() {
		System.oUt.println("FU中无参构造方法被调用");
	}

	public Fu(int age) {
		System.oUt.println("FU中带参构造方法被调用");
	}
}

public class Zi extends Fu {
	public Zi() {
		System.out.println("Zi中无参构造方法被调用");
	}

	public Zi(int age) {
		System.oUt.println("Zi中带参构造方法被调用");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象
		zi z = new Zi();
		Zi z2 = new Zi(20);
		/*
			Fu中无参构造方法被调用
			Zi中无参构造方法被调用
			Fu中无参构造方法被调用
			Zi中带参构造方法被调用
		*/
	}
}

子类中所有的构造方法默认都会访问父类中无参的构造方法

  • 为什么呢?
  • 因为子类会继承父类中的数据,可能还会使用父类的数据,所以,子类初始化之前,一定要先完成父类数据的初始化。 (子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类就无法使用父类的数据)
  • 每一个子类构造方法的第一条语句默认都是:super()

怎么初始化?

  • 在一个类中,若是构造方法的第一行代码没有显式的调用 super(…) 或者 this(…);那么 Java 默认都会调用 super(),含义是调用父类的无参数构造方法。这里的 super() 可以省略。构造方法的第一条语句默认都是: super()

如果父类没有无参构造方法,那么她一定有有参构造,在子类的构造方法中,手动把 super() 写出来,然后再手动调用父类的有参构造方法即可,在 super() 中传入与父类有参构造方法相对应的参数,但是此时,数据就写死了。


方法一:在子类中通过 super(参数) 的形式,调用父类的有参构造方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Fu {
	//public Fu() {
	//	System.oUt.println("FU中无参构造方法被调用");
	//}

	public Fu(int age) {
		System.oUt.println("FU中带参构造方法被调用");
	}
}

public class Zi extends Fu {
	public Zi() {
		//super();
		super(20);
		System.out.println("Zi中无参构造方法被调用");
	}

	public Zi(int age) {
		//super();
		super(age);
		System.oUt.println("Zi中带参构造方法被调用");
	}
}

方法二:子类无参构造方法通过 this 调用重载的构造方法,从而调用父类的带参构造方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Fu {
	//public Fu() {
	//	System.oUt.println("FU中无参构造方法被调用");
	//}

	public Fu(int age) {
		System.oUt.println("FU中带参构造方法被调用");
	}
}

public class Zi extends Fu {
	public Zi() {
		this(10);
	}

	public Zi(int age) {
		super(age);
	}
}

This() 是调用自己的重载构造器,super() 调父类的构造器。 This()super() 都必须放在构造方法的第一行, 两者不能共存。


构造方法访问总结

1.创建子类对象的时候, 一定会优先创建父类对象;

2.如果父类中没有空参数构造方法,那么建议在子类的构造方法的第一行,手动调用父类的带参数构造方法;

  • 调用格式:super(实参)

继承中成员方法的访问特点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Fu {
	public void show() {
		System.oUt.println("Fu中show()方法被调用");
	}
}

public class Zi extends Fu {
	public void method() {
		System.out.println("Zi中method()方法被调用");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.method();  //Zi中method()方法被调用
		z.show();  //Fu中show()方法被调用
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Fu {
	public void show() {
		System.oUt.println("Fu中show()方法被调用");
	}
}

public class Zi extends Fu {
	public void method() {
		System.out.println("Zi中method()方法被调用");
	}
	
	
	public void show() {
		System.oUt.println("Zi中show()方法被调用");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.method();  //Zi中method()方法被调用
		z.show();  //Zi中show()方法被调用
	}
}
 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
public class Fu {
	public void show() {
		System.oUt.println("Fu中show()方法被调用");
	}
}

public class Zi extends Fu {
	public void method() {
		System.out.println("Zi中method()方法被调用");
	}
	
	
	public void show() {
		super.show();
		System.oUt.println("Zi中show()方法被调用");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Zi z = new Zi();
		z.method();  //Zi中method()方法被调用
		z.show();  //Fu中show()方法被调用   Zi中show()方法被调用
	}
}

通过子类对象访问一个方法

  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错(不考虑父亲的父亲…)

继承树追溯

属性/方法查找顺序:(比如:查找变量h)

  • 查找当前类中有没有属性 h
  • 依次上溯每个父类,查看每个父类中是否有 h, 直到 Object
  • 如果没找到,则出现编译错误。
  • 上面步骤,只要找到 h 变量,则这个过程终止。

构造方法调用顺序:

构造方法第一句总是:super(…) 来调用父类对应的构造方法。所以,流程就是:先向上追溯到 Object, 然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。


注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FatherClass2 {
	public FatherClass2() {
		System.out.println("创建FatherClass");
	}
}

class ChildClass2 extends FatherClass2 {
	public Childclass2() {
		System.out.printIn("创建ChildClass");
	}
}

public class TestSuper02 {
	public static void main(String[] args) {
		System.out.println("开始创建一个ChildClass对象...");
		new ChildClass2();
	}
}

开始创建一个ChildC1ass对象...
创建FatherClass
创建ChildClass

Super() 加不加都有,执行 childclass2 时,因为有 super(), 所以先调用父类的无参构造方法

虽然 newChildClass2() 先建的是子类的对象,但实际上先建好的是父类的对象

如果你自己加了super(), 那么调用的是你加的 super()


/images/java/JavaSE 进阶 (1) 继承/7.png
(图7)

如上图所示:new ChildClass2() 当这个对象还没建好时,通过 super(),就去 FatherClass2 中的构造器,FatherClass2 第一句也是 Super(),当 FatherClass() 对象还没建好,通过 super(),就去 Object 的构造器中,Object 建好以后,这个方法结束了,才会建 FarherClaa2(),当 FatherClass2() 建好,才会建 ChildClass2()


这个结构建好之后,在外面再加个外壳,外面调用的时候,只能从 ChildClass() 开始,如果 ChildClass() 里没有这个方法,去父类中找,没有的话,再去上一层找,直到找到,如果转一圈没找到,报错。

只能从 ChildClass() 开始,父类的对象是不对外的,你是访问不了的。


抽象类是不能建对象的,父类是抽象类怎么办,那个对象对于我们来说,我们是建不了的,但是对于虚拟机,他的内部是会建一个对象的,用来代表这个含义,但是我们访问不了,那个语法是约束程序员的,但是它内部也会建一个对象来代表抽象类的信息。

方法重写 override

方法重写概述

  • 子类中出现了和父类中一模一样的方法声明

方法重写的应用

  • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容

@Override

  • 是一个注解
  • 可以帮助我们检查重写方法的方法声明的正确性
 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
public class Phone {
	public void call(String name) {
		System.oUt.println("给" + name + "打电话");
	}
}

public class NewPhone extends Phone {

	@Override
	public void call(String name) {
		System.out.println("开启视频功能");
		//System.oUt.println("给"+name+"打电话")
		super.call(name);
	}
}

public class PhoneDemo {
	public static void main(String[] args) {
		//创建对象,调用方法
		Phone p = new Phone();
		p.call("林青霞");
		
		System.out.println("------------");
		
		NewPhone np = new NewPhone();
		np.call("林青霞");
	}
}

Override 重写,也可以称作覆盖,例如,一个盒子上盖了一个盆,并没有把盒子删除,父类的方法还在,只是被覆盖了


父类中私有方法不能被重写

父类 静态方法,子类必须 通过静态方法 进行 重写,父类 非静态方法,子类也 必须通过非静态方法 进行 重写


注意:静态方法不能被重写!如果子类中,也存在一个方法声明一模一样的方法,可以理解为,子类将父类中同名的方法,隐藏了起来,并非是方法重写!


子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件。


方法的重写需要符合下面的三个要点:

  • 1.==:方法名、形参列表相同。
  • 2.:返回值类型和声明异常类型,子类小于等于父类。
  • 3.:访问权限,子类大于等于父类。

"≤":返回值类型和声明异常类型,子类小于等于父类。

父类 Person 有个 getFriends() 方法,返回值类型为 Person

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Person /*extends Object*/{
	String name;
	int height;

	public void rest(){
		System.out.println("休息!");
	}

	public Person getFriends(){
		return new Person();
	}
}

子类对父类 PersongetFriends() 方法进行重写,返回值类型为 Student

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void rest() {
	System.out.println("回宿舍睡会!");
}

public Student getFriends() {
	return new Student();
}

Student(String name,int height,int score) {
	this.name = name;
	this.height = height;
	this.score = score;
}

Student 的范围 ≤ Person 的范围,满足要求

当返回值的范围是 ObjectObject>Person,是不行的,子类进行重写返回范围必须 ≤ 父类的范围

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void rest() {
	System.out.println("回宿舍睡会!");
}

public Object getFriends() {
	return new Student();
}

Student(String name,int height,int score) {
	this.name = name;
	this.height = height;
	this.score = score;
}

访问修饰符

权限修饰符(控制权限): private、default、protected、public

状态修饰符( 控制状态): final、static、abstract

/images/java/JavaSE 进阶 (1) 继承/8.png
(图8)
/images/java/JavaSE 进阶 (1) 继承/9.png
(图9)

越往上,权限越窄,假如父类定义 default,那么子类可以 protected

Private 表示私有,只有自己类能访问

Default 表示没有修饰符修饰,只有同一个包的类能访问

组合

我们可以通过继承方便的复用已经定义类的代码。还有一种方式,也可以方便的实现 “代码复用”,那就是:“组合”。

“组合” 不同于继承,更加灵活。

“组合” 的核心就是 “将父类对象作为子类的属性”,然后,“子类通过调用这个属性来获得父类的属性和方法”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Dog {
	int height;

	public void shout() {
		System.out.println("汪汪汪!");
	}
}

class Taidi {
	Dog dog = new Dog();
}

public class Demo {
	public static void main(String[] args) {
		Taidi t = new Taidi();
		t.dog.shout();
		t.dog.height = 101;
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Dog {
	public void shout() {
		System.out.println("汪汪汪!");
	}
}

class Tugou {
	Dog d = new Dog();
}

public class Demo {
	public static void main(String[] args) {
		Tugou t = new Tugou();
		t.d.shout();
	}
}

想使用另一个人的东西,有两种思路:

① 认他做爸爸,也就是继承,把他的东西继承过来。

② 把他吸收了,收购并购,也就是组合。

 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
class Person {
	String name;
	int height;
	
	public void rest() {
		System.out.printin("休息一会!")
	}
}

class Student {
	Person person = new Person();
	String major;//专业
	
	public void study() {
		System.out.printin("学习!")
	}
	
	public Student(String name,int height,string major) {
		//天然拥有父类的属性
		this.person.name = name;

		this.person.height = height;

		this.person.rest();
		
		this.major = major;
	}
}

public class Test {
	public static void main(String[] args) {
		Student s = new Student("高淇"172"Java");
		s.person.rest();
		s.study();
	}
}

组合比较灵活,继承只能有一个父类,但是组合可以有多个属性。所以,有人声称 “组合优于继承,开发中可以不用继承”,但是,不建议大家走极端。

继承除了代码复用,也能方便我们对事务建模。所以,对于 “is - a” 关系建议使用 继承“has - a” 关系建议使用 组合

比如:上面的例子,Student is a Person 这个逻辑没问题,但是:Student has a Person 就有问题了。这时候,显然继承关系比较合适。

再比如:笔记本和芯片的关系显然是 "has - a" 关系,使用组合更好。

Java 中继承的注意事项

  • Java 中类只支持单继承,不支持多继承
  • Java 中类支持多层继承
/images/java/JavaSE 进阶 (1) 继承/10.png
(图10)
/images/java/JavaSE 进阶 (1) 继承/11.png
(图11)

案例 1(老师和学生)

需求:定义老师类和学生类,然后写代码测试;最后找到老师类和学生类当中的共性内容,抽取出一个父类, 用继承的方式改写代码,并进行测试

 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
public class Person {
	private String name;
	private int age;

	public Person() {
	}

	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	
	
	//set/get......
}

public class Teacher extends Person {
	public Teacher() {
	}

	public Teacher(String name,int age) {
		super(name,age);
	}

	public void teach() {
		System.out.println("用爱成就每一位同学");
	}
}

public class Demo {
	public static void main(String[] args) {
		//创建老师对象并进行测试
		Teacher t1 = new Teacher();
		t1.setName("林青霞");
		t1.setAge(33);
		System.out.println(t1.getName()+ "," + t1.getAge());
		t1.teach();
		
		Teacher t2 = new Teacher("风清扬"36);
		System.out.println(t2.getName()+ "," + t2.getAge());
		t2.teach();
	}
}

案例2(项目经理和程序员)

需求:请使用继承的思想设计出项目经理类和程序员类,并进行测试。

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class Employee {
	private String id;  //id
	private String name; //姓名
	private double salary; //薪水

	public Employee() {
	}

	public Employee(String id,String name,double salary) {
		this.id= id;
		this.name = name;
		this.salary = salary;
	}
	
	public void work() {
		System.out.println("员工需要工作");
	}
	
	//set/get......
}

//项目经理类
public class Manager extends Employee {
	private double bonus; //奖金

	public Manager() {
	}
	
	public Manager(String id,String name,double salary,double bonus) {
		super(id,name,salary);
		this.bonus = bonus;
	}
	
	@Override
	public void work() {
		System.out.println("项目经理和客户谈需求");
	}
	
	//set/get.....
}

//程序员类
public class Programmer extends Employee {
	public Programmer() {
	}
	
	public Programmer(String id,String name,double salary) {
		super(id,name,salary);
	}
	
	@Override
	public void work() {
		System.out.println("程序员根据需求来编写代码");
	}
}

public class EmployeeDemo {
	public static void main(String[] args) {
		//创建项目经理类的对象并进行测试
		Manager m1 = new Manager();
		m1.setId("001");
		m1.setName("林青霞");
		m1.setSalary(10000.00);
		m1.setBonus(20000.00);
		System.out.println(m1.getId() + "," + m1.getName() + "," + m1.getSalary() + "," + m1.getBonus());
		m1.work();
		
		Manager m2 = new Manager("001", "林青霞", 10000.00, 20000.00);
		System.out.println(m2.getId() + "," + m2.getName() + "," + m2.getSalary() + "," + m2.getBonus());
		m2.work();
	}
}

final 关键字

状态修饰符

  • final(最终态)
  • static(静态)

final 关键字是最终的意思,可以修饰成员方法、成员变量、类


final 修饰的特点

  • 修饰方法:表明该方法是最终方法,不能被重写
  • 修饰变量:表明该变量是常量,不能再次被赋值
  • 修饰类:表明该类是最终类,不能被继承

只要加了 final 那么就是不可扩展,不可更改的

final 修饰局部变量

Final 修饰基本类型变量时,指的是基本类型的 数据值 不能发生改变

Final 修饰引用类型变量时,引用类型的 地址值 不能发生改变,但是地址里面的内容是可以发生改变的,图中,当你重新给 S 赋新的地址时,就会报错

1
2
3
public class Student {
  public int age = 20;
}
/images/java/JavaSE 进阶 (1) 继承/12.png
(图12)

static 关键字

static 关键字是静态的意思,可以修饰成员方法、成员变量


static 声明的变量叫做静态变量,用 static 声明的方法叫做静态方法

只要看到static修饰的,那就是位于类中,而不是对象中


static 修饰的特点:

  • 被类的所有对象共享,这也是我们判断是否使用静态关键字的条件
  • 可以通过类名调用,当然,也可以通过对象名调用,推荐使用类名调用

在类中,用 static 声明的成员变量为静态成员变量,也称为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:

  • 为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化
  • 对于该类的所有对象来说,static 成员变量只有一份。被该类的所有对象共享!!
  • 一般用 “类名.类属性/方法” 来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)
  • static 方法中不可直接访问非 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
31
public class Student {
	public String name;  //姓名
	public int age;  //年龄

	//public String university; //学校
	public static String university; //学校

	public void show() {
		System.out.println(name + "," + age + "," + university);

	}
}

public class StaticDemo {
	public static void main(String[] args) {
		Student.university = "北京大学"
	
		Student s1 = new Student();
		s1.name = "林青霞"
		s1.age = 33;
		//s1.university = "北京大学";
		s1.show();
		
		Student s2 = new Student();
		s2.name = "风清扬"
		s2.age = 36;

		//s2.university = "北京大学":
		s2.show();
	}
}

static 的访问特点

非静态的成员方法

  • 能访问静态的成员变量
  • 能访问非静态的成员变量
  • 能访问静态的成员方法
  • 能访问非静态的成员方法

静态的成员方法

  • 能访问静态的成员变量
  • 能访问静态的成员方法

总结成一句话就是:静态成员方法只能访问静态成员


原理解析

/images/java/JavaSE 进阶 (1) 继承/13.png
(图13)

User u = new User();

红色标记的 User 在开始时,加载 User 类信息,其实方法区中的 User 也是一个对象,只不过是用来表示类信息的一个对象,加载 User 类的什么信息呢? 加载的是:代码结构、static 的属性(直接赋值)和方法【company指向一个字符串】


/images/java/JavaSE 进阶 (1) 继承/14.png
(图14)

此时,User 执行完毕

Java 中,静态方法不能使用 this 关键字。this 关键字是用来引用当前对象的实例,而 静态方法属于类本身,而 不依赖于任何对象实例。因此,在静态方法中使用 this 关键字会导致编译错误。

当你在静态方法中使用 this 时,编译器会报错,因为它不知道该引用哪个对象实例。只有非静态方法可以访问和操作对象的实例变量和方法,因此非静态方法中可以使用 this 关键字。

普通的属性和普通的方法,都是在堆中的对象里放着,从属于对象,他们之间可以互相访问(login() 可以调用 idnamepwd),而 printCompany()company 是在方法区中的类信息里放着的,他们之间可以互相访问


/images/java/JavaSE 进阶 (1) 继承/15.png
(图15)

方法区中的 User 类可以看作是一个笔记本模具,模具之间可以互相调用,所以方法区中的 companyprintCompany() 可以互相调用,而堆里的对象是根据模具制造出来的笔记本,模具无法调用笔记本里的内容,反过来,堆中的对象可以调用方法区中类信息中的数据吗? 可以,这是因为如果堆中的对象存在,那么方法区中的类信息必然一定被加载了,我持有了类的信息,我可以直接去调方法区中类信息里的数据,但是,反过来就不行,如果堆中有多个对象,方法区中的静态方法不知道调用哪个对象,其次,不见得堆中有对象可以让静态方法调用,因为对于方法区中的静态方法来说,我只能确定类信息被加载了,对象有没有创建我也不知道,但是对于堆中的对象来说,对象都有了,类信息一定被加载了,我一定可以找到我的模具

main 方法的详细说明

main 方法格式的详细说明

public static void main(String[] args){}

  • public 被 jvm 调用,访问权限足够大
  • static 被 jvm 调用,不用创建对象,直接类名访问
  • void 被 jvm 调用,不需要给 jvm 返回值
  • main 一个通用的名称,虽然不是关键字,但是被 jvm 识别
  • String[] args 以前用于接收键盘录入的,现在我们有了 Scanner,不需要他了
  • 演示 args 接收数据
1
2
3
4
5
public class HelloWorld {
	public static void main(String[] args) {
		System.out.println(args.length);
	}
}

怎么给 args 添加数据

/images/java/JavaSE 进阶 (1) 继承/16.png
(图16)
/images/java/JavaSE 进阶 (1) 继承/17.png
(图17)

提供多个数据,数据之间用空格隔开

1
2
3
4
5
6
7
8
9
public class HelloWorld {
	public static void main(String[] args) {
		System.out.println(args.length);
		
		for(int i=0; i<args.length; i++) {
			System.out.println(args[i]);
		}
	}
}

工具类的制作和使用

static 的应用

可以用 static 完成工具类的编写,通过 private 修饰构造方法,让创建对象调用方法的方式行不通,在方法前加上 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
public class ArrayTool {
	private ArrayTool() {  //自己不提供, 默认会有一个无参构造方法, 所以自己主动提供一个私有的无参构造方法
	}

	public static int getMax(int[] arr) {
		int max = arr[0];
		
		for(int i = 1; i < arr.length; i++) {
			if(arr[i] > max){
				max = arr[i];
			}
		}
		
		return max;
	}
}

public class ArrayDemo {
	public static void main(String[] args) {
		//定义一个数组
		int[] arr = {12, 56, 78, 93, 40};
		/*
			ArrayTool at = new ArrayTool(); 构造方法是私有的
			int max = at.getMax(arr);  无法通过对象.方法名来调用
		*/
		int max = ArrayTool.getMax(arr);
		System.oUt.println("数组中的最大值是:" + max);
	}
}

工具类的特点:

  • 构造方法私有
  • 成员用 static 修饰

jar 包和帮助文档

定义的工具类只能在当前模块中使用,实际中,应该在任何项目都可使用,为了让工具类做到一次书写多次使用,这个时候需要用到 jar 包来完成

  • Jar 包: 也就是后缀名为 .jar 的文件,也叫做 jar 文件
  • Jar 文件: (java 归档,英语: javaARchive)是一种软件包文件格式,一般情况下 jar 文件中打包了多个 class 文件
  • 简单理解: jar 包就是 .class 文件的压缩包

Jar 包制作

 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
/**
* 这是对数组进行操作的工具类
*
* @author piliqiu
* @version 1.0
*/

public class ArrayTool {
	private ArrayTool() {
	}
	
	/**
	 * 获取数组中最大值
	 *
	 * @param arr 参数是一个int数组
	 * @return 返回数组中的最大值
	 */

	public static int getMax(int[] arr) {
		int max = arr[0];
		for(int i = 1; i < arr.Length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		return max;
	}
}
/images/java/JavaSE 进阶 (1) 继承/18.png
(图18)
/images/java/JavaSE 进阶 (1) 继承/19.png
(图19)

Module: 选择要制作 jar 包的模块

/images/java/JavaSE 进阶 (1) 继承/20.png
(图20)
/images/java/JavaSE 进阶 (1) 继承/21.png
(图21)
/images/java/JavaSE 进阶 (1) 继承/22.png
(图22)

Jar 包的使用

/images/java/JavaSE 进阶 (1) 继承/23.png
(图23)

创建一个目录 lib,将 jar 包放进去,然后右键 Add as Library


/images/java/JavaSE 进阶 (1) 继承/24.png
(图24)

点击 OK,这样我们就把制作好的 jar 包,导入到我们要使用的模块中了


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import com.piliqiu.ArrayTool;

public class ArrayDemo {
	public static void main(String[] args) {
		//定义一个数组
		int[] arr = {12, 56, 78, 93, 40};
		//需求:获取数组中最大值
		int max = ArrayTool.getMax(arr);
		System.oUt.println("数组中的最大值是:" + max);
	}
}

制作 api 文档

制作帮助文档,首先要编写文档注释

/images/java/JavaSE 进阶 (1) 继承/25.png
(图25)
/images/java/JavaSE 进阶 (1) 继承/26.png
(图26)

要在 @author、@version 前打上对勾,他才会解析工具类中的 @author、@version 注释

Output directory:保存目录

生成的文档是 html 格式的,可以通过工具转换成 chm 格式的

-encoding UTF-8 -charset UTF-8:防止乱码

/images/java/JavaSE 进阶 (1) 继承/27.png
(图27)

静态初始化块

构造方法用于对象的初始化,静态初始化块用于类的初始化操作,在静态初始化块中不能直接访问非 static 成员


静态初始化块执行顺序

  • 上溯到 Object 类,先执行 Object 的静态初始化块,再向下执行子类的静态初始化块,直到类的静态初始化块为止。
  • 构造方法执行顺序和上面顺序一样!!

静态属性怎么初始化

  • 直接赋值,例如: static String company = “华为”;
  • 用静态初始化块

Static 从属于类,修饰的变量就做类变量、静态变量,修饰的方法叫做类方法,修饰的初始化叫类初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class User {
	int m_id;
	String m_name;
	String m_pwd;
	static String m_school;
	
	static {   //用来对类的初始化操作,例如static的成员变量,但在块中必须是静态函数与静态成员
		System.out.println("执行类的初始化工作!");
		m_school = "北京大学"
		printSchool();
	}
	
	public static void printSchool(){
		System.out.printIn(m_school);
	}
	
	public static void main(String[] args){
		User wang = null;
	}
}

包机制(package、import)

package

包机制是 java 中管理类的重要手段。开发中,我们会遇到大量同名的类,通过包我们很容易 解决类重名 的问题,也可以把功能相似的类放到同一个包中,从而 实现对类的有效管理 对于 ,相当于 文件夹 对于 文件 的作用。

java 生态体系是开源的生态体系,意思是我在这写一个类,发布出去后,别人也可以调,全球互联网这么多人,这么多公司,大家都起一个 hello world,这样就乱套了,java 想出了一个办法,包名一般是域名,按域名来规划,一般每个公司都有域名,用域名作为包的名字,全世界所有公司域名肯定不会重复,这样可以天然的把这些公司区分开


我们通过 package 实现对类的管理,package 的使用有两个要点:

  1. 通常是类的第一句非注释性语句。
  2. 包名:域名倒着写即可,再加上模块名,便于内部管理类。
1
2
3
4
5
com.sun.test;
com.oracle.test;
cn.sxt.gao.test;
cn.sxt.gao.view;
cn.sxt.gao.view.model;

每个公司都有一个域名,域名只会相似,不会重复,这样就不会出现,每个人都写一个 hello world 开源共享,让人分不清,上面图示的域名为 cn.sxt,在域名之后,可以加项目的名称,模块的名称。

类似于上图第一行和第二行,由于包名的不同,可以把同名的类区分开


注意事项

  • 写项目时都要加包,不要使用默认包。
  • com.gaocom.gao.car,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。

com.gaocom.gao.car 逻辑上确实是父子的关系,但在 Java 的包管理机制中,它们是两个独立的包。假如说 com.gaocom.gao.car 中分别有个 User 类,那么我导包的时候,com.gao 中的 User 类肯定和 com.gao.car 中的 User 类不是一个


包名位于非注释的第一句

/images/java/JavaSE 进阶 (1) 继承/28.png
(图28)

JDK 中的主要包

Java 中的常用包 说明
java.lang 包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread,提供常用功能
java.awt 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)
java.net 包含执行与网络相关的操的类
java.io 包含能提供多种 输入/输出 功能的类
java.util 包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数

Java.lang:langlanguage 的缩写

Java.awt:开发具有窗口化的软件,但是在 java 企业里不用 java 开发窗口化的软件

Java.net:见名知义,网络相关

导入类 import

如果我们要使用其他包的类,需要使用 import 导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import 导入后,便于编写代码,提高可维护性。

注意要点

  • Java 会默认导入 java.lang 包下所有的类,因此这些类我们可以直接使用。
  • 如果导入两个同名的类,只能用 包名+类名 来显示调用相关类:java.util.Date date = new java.util.Date();

举例说明

/images/java/JavaSE 进阶 (1) 继承/29.png
(图29)

此时 user 类在另一个包中,无法直接使用


/images/java/JavaSE 进阶 (1) 继承/30.png
(图30)

在不导入包名 com.bjsxt.test2.User 的情况下。创建对象的时候用 包名+类名 来创建对象,极其麻烦


/images/java/JavaSE 进阶 (1) 继承/31.png
(图31)

导入包名 com.bjsxt.test2.User 后,创建对象就方便了许多


/images/java/JavaSE 进阶 (1) 继承/32.png
(图32)

在导入同一个包中的多个类时,我么可以用 import.com.bjsxt.test2.* 来代替一个一个导入,将 test2 中所有的类都导入,但是这种方式存在一个问题,导入这个包中所有的类,会降低编译速度,不会降低运行速度,因为你就编译一次,程序就开始运行


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.sql.Date;
import java.util.*;  //导入该包下所有的类。会降低编译速度,但不会降低运行速度。

public class Test {
	public static void main(String[] args) {
		//这里指的是java.sql.Date
		Date now;
		
		//java.util.Date因为和java.sql.Date类同名,需要完整路径
		java.util.Date now2 = new java.util.Date();
		System.out.printIn(now2);
		
		//java.util包的非同名类不需要完整路径
		Scanner input = new Scanner(System.in);
	}
}

java.sql.Date; 包里有 Date 类,java.util,*; 里也有 Date 类,此时,使用的时候,需要写完整的包名

静态导入

静态导入(static import)是在 JDK 1.5 新增加的功能,其作用是用于 导入指定类的静态属性和静态方法,这样我们就可以直接使用静态属性和静态方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package cn.sxt;
//以下两种静态导入的方式二选一即可
import static java.lang.Math.*;  //导入Math类的所有静态属性和方法
import static java.lang.Math.PI;  //导入Math类的PI属性

public class Test2 {
	public static void main(String[] args) {
		System.out.println(PI);
		System.out.println(random());
	}
}

此时,import static java.lang.Math.*; 导入的是 Math 类里的所有静态属性,而不是 Math

Import static java.lang.Math.PI; 导入的是 PI 属性,而不是 Math

instanceof(实例) 运算符

instanceof 是二元运算符,左边是对象,右边是类;当左边的对象是右边的类或子类所创建的对象时,返回 true;否则,返回 false

1
2
3
4
5
6
7
public class Test {
	public static void main(String[] args) {
		Student s = new Student("张三"172"Java");
		System.out.println(s instanceof Person);  //true
		System.out.println(s instanceof Student);  //true
	}
}

s instanceof Object 输出也是 true

Java 中,所有的类都是 Object 类的子类。这意味着,如果一个对象是某个类的实例,那么它也是 Object 类的实例。由于 Student 继承自 Person,而 Person 继承自 Object,因此任何 Student 对象都是 Object 对象。


0%