继承概述
(图1
)
(图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)子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如:父类私有的属性和方法)
(图3
)
(4)Java
只支持单继承,不支持多继承,但支持多层继承,接口支持多继承。
多继承会引起混乱,使得继承链过于复杂,系统难于维护。
如果是多继承,每个类都有多个父类,那么就乱套了,成网状结构,虽然功能很强大。
维承的好处和弊端
继承好处:
提高了代码 复用性 (多个类相同的成员可以放到同一个类中)
提高了代码的 维护性 (如果方法的代码需要修改,修改一处即可)
方便建模
继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时,子类实现也不得不跟着变化,削弱了子类的独立性。
什么时候使用继承?
继承体现的是:is a 关系 (什么是什么的一种)
假设法:我有两个类 A
和 B
,如果他们满足 A
是 B
的一种,或者 B
是 A
的一种,就说明他们存在继承关系,这个时候就可以考虑使用继承来体现,否则就不能滥用继承
举例:苹果和水果,猫和动物,猫和狗(不存在继承关系)
(图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
的直接父类,person
是 Object
的直接子类,所有类都是 Object 的子类 ,差别在于是不是直接子类,直接父类,默认情况下说的父类就是直接父类。
(图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 关键字访问;
(图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.如果父类中没有空参数构造方法,那么建议在子类的构造方法的第一行,手动调用父类的带参数构造方法;
继承中成员方法的访问特点
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()
(图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 ();
}
}
子类对父类 Person
的 getFriends()
方法进行重写,返回值类型为 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 的范围,满足要求
当返回值的范围是 Object
,Object>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
(图8
)
(图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 中类支持多层继承
(图10
)
(图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
关键字是最终的意思,可以修饰成员方法、成员变量、类
final
修饰的特点
修饰方法:表明该方法是最终方法,不能被重写
修饰变量:表明该变量是常量,不能再次被赋值
修饰类:表明该类是最终类,不能被继承
只要加了 final
那么就是不可扩展,不可更改的
final 修饰局部变量
Final
修饰基本类型变量时,指的是基本类型的 数据值 不能发生改变
Final
修饰引用类型变量时,引用类型的 地址值 不能发生改变,但是地址里面的内容是可以发生改变的,图中,当你重新给 S
赋新的地址时,就会报错
1
2
3
public class Student {
public int age = 20 ;
}
(图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 的访问特点
非静态的成员方法
能访问静态的成员变量
能访问非静态的成员变量
能访问静态的成员方法
能访问非静态的成员方法
静态的成员方法
总结成一句话就是:静态成员方法只能访问静态成员
原理解析
(图13
)
User u = new User();
红色标记的 User
在开始时,加载 User
类信息,其实方法区中的 User
也是一个对象,只不过是用来表示类信息的一个对象,加载 User 类的什么信息呢? 加载的是:代码结构、static 的属性(直接赋值)和方法【company指向一个字符串】
(图14
)
此时,User
执行完毕
在 Java
中,静态方法不能使用 this
关键字。this
关键字是用来引用当前对象的实例,而 静态方法属于类本身 ,而 不依赖于任何对象实例 。因此,在静态方法中使用 this
关键字会导致编译错误。
当你在静态方法中使用 this
时,编译器会报错,因为它不知道该引用哪个对象实例。只有非静态方法可以访问和操作对象的实例变量和方法,因此非静态方法中可以使用 this
关键字。
普通的属性和普通的方法,都是在堆中的对象里放着,从属于对象,他们之间可以互相访问(login()
可以调用 id
、name
、pwd
),而 printCompany()
和 company
是在方法区中的类信息里放着的,他们之间可以互相访问
(图15
)
方法区中的 User
类可以看作是一个笔记本模具,模具之间可以互相调用,所以方法区中的 company
和 printCompany()
可以互相调用,而堆里的对象是根据模具制造出来的笔记本,模具无法调用笔记本里的内容,反过来,堆中的对象可以调用方法区中类信息中的数据吗? 可以,这是因为如果堆中的对象存在,那么方法区中的类信息必然一定被加载了,我持有了类的信息,我可以直接去调方法区中类信息里的数据,但是,反过来就不行,如果堆中有多个对象,方法区中的静态方法不知道调用哪个对象,其次,不见得堆中有对象可以让静态方法调用,因为对于方法区中的静态方法来说,我只能确定类信息被加载了,对象有没有创建我也不知道,但是对于堆中的对象来说,对象都有了,类信息一定被加载了,我一定可以找到我的模具
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
添加数据
(图16
)
(图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 );
}
}
工具类的特点:
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 ;
}
}
(图18
)
(图19
)
Module
: 选择要制作 jar
包的模块
(图20
)
(图21
)
(图22
)
Jar 包的使用
(图23
)
创建一个目录 lib
,将 jar
包放进去,然后右键 Add as Library
(图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 文档
制作帮助文档,首先要编写文档注释
(图25
)
(图26
)
要在 @author、@version
前打上对勾,他才会解析工具类中的 @author、@version
注释
Output directory
:保存目录
生成的文档是 html
格式的,可以通过工具转换成 chm
格式的
-encoding UTF-8 -charset UTF-8
:防止乱码
(图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
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.gao
和 com.gao.car
,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。
com.gao
和 com.gao.car
逻辑上确实是父子的关系,但在 Java
的包管理机制中,它们是两个独立的包。假如说 com.gao
和 com.gao.car
中分别有个 User
类,那么我导包的时候,com.gao
中的 User
类肯定和 com.gao.car
中的 User
类不是一个
包名位于非注释的第一句
(图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:lang
是 language
的缩写
Java.awt:开发具有窗口化的软件,但是在 java
企业里不用 java
开发窗口化的软件
Java.net:见名知义,网络相关
导入类 import
如果我们要使用其他包的类,需要使用 import
导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import
导入后,便于编写代码,提高可维护性。
注意要点
Java
会默认导入 java.lang
包下所有的类,因此这些类我们可以直接使用。
如果导入两个同名的类,只能用 包名+类名
来显示调用相关类:java.util.Date date = new java.util.Date();
举例说明
(图29
)
此时 user
类在另一个包中,无法直接使用
(图30
)
在不导入包名 com.bjsxt.test2.User
的情况下。创建对象的时候用 包名+类名
来创建对象,极其麻烦
(图31
)
导入包名 com.bjsxt.test2.User
后,创建对象就方便了许多
(图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
对象。