(JAVA)类与对象(进阶)
一、包
1. 包的简介
-
每个包对应一个文件夹,文件夹里还可以有子类文件夹,就是子包。
-
当你需要用到包的概念时,需要在
.java
文件的第一条语句,声明该文件是在什么包下
package learn.java_.test;
public class Test {
}
2. 包的使用
-
这时候可能有朋友疑问了:为什么不把文件路径写全呢?
我的理解:你声明该文件的第一个包名为
learn
,那么在当你需要使用Test
类时,只需要把文件夹learn
直接复制到你使用的位置,就可以使用了。因为前面没写的部分,会默认在你使用的当前目录下去找learn\java_\test
。 -
示例:
import learn.java_.test.Test; public class paper { public static void main(String[] args) { Test t1 = new Test(); } }
在这里,在paper类中使用import learn.java_.test.Test;
来引包,会在paper.java所在目录下即src中找到learn\java_\test
下的Test.java文件。当然如果把.Test改为.*是引入该test包下所有的文件。
-
这里思考一下,既然上面示例中import会找到Test类,那么为什么我们还要再声明
package learn.java_.test;
呢?因为他们的先后顺序不要搞错了,其实只有当你在该Test.java文件的第一条语句声明
package learn.java_.test;
后,才能在paper类中import learn.java_.test.Test;成功。因为如果在该Test.java
文件没有使用package语句声明类所在的包时,java默任包的路径为当前文件夹,例如:在paper类中使用Test类,显然paper类的当前文件夹src中并没有Test类,因此会报错的。 -
在同一包中的类默认情况下可以互相访问。
二、🐯继承
1. 简介
-
一个子类只能有一个父类,而一个父类可以有多个子类。
-
父类实际上是所有子类的公共成员的集合。
-
被继承的类称为父类或者超类,由继承而得到的类称为子类。
-
使用关键字
extends
,示例://A继承了B public class A extends B{ } class B { }
2. 使用
-
子类会继承父类可访问的成员变量和方法,可以直接通过子类对象来调用。
-
示例:
public class A extends B{ public static void main(String[] args) { A a = new A(); //访问父类的成员 System.out.println(a.b); a.b(); } } class B { int b = 0; public void b() { System.out.println("B"); } }
3. super
-
super
是在子类方法中使用,来代表所继承的父类的引用。 -
用于在子类方法中访问父类的属性、方法,但不能访问
private
所修饰的父类成员。 -
使用方法:super.变量名; super.方法;
示例:
public class A extends B{ void inB(){ System.out.println(super.b); super.isB(); } } class B { int b; void isB(){} }
4. 构建方法
-
在执行子类的构造方法前,会先自动调用父类的无参构造方法。
-
如果父类没有无参构造器,则必须在子类的构造器中的第一条语句使用
super
来初始化父类
示例:public class A extends B{ public A(int b) { //调用B中的public B(int b) super(b); } } class B { int b; public B(int b) { this.b = b; } }
-
使用方法:super(参数列表)
5.🐻方法重写
-
也称方法覆盖(override)
-
在子类中如果有一个方法,其方法名、返回类型、参数都和父类中的某一方法一样,那么该子类方法就重写了父类同名方法的功能
-
示例:
public class A extends B{ //重写了B的say方法 void say() { System.out.println("A"); } } class B { void say() { System.out.println("B"); } }
-
重写的方法的返回类型可以和父类方法一样,或者是父类返回类型的子类
-
👿重写的方法的访问权限可以比父类的大,但是不能比父类的小。
如:示例中第3行省略的访问修饰符可以改成public,但是不能是protected。
-
子类中不能重写父类中有
final
和static
所修饰的方法。
对于成员变量没有重写的概念。
6. 对象的多态
-
一个对象的编译类型和运行类型可以不一致
-
编译类型是在定义时变量名前的类型,不能改变
-
运行类型是创建对象时
new
的类型,可以更改。 -
如:
public class A extends B{ public static void main(String[] args) { B b = new A(); } } class B { }
对象
b
的编译类型是B
,而运行类型是A
。
6.1 向上转型
-
父类的引用指向了子类的对象,父类类型 引用名 = 子类对象
-
如:
public class A extends B{ public static void main(String[] args) { //父类的引用指向了子类的对象 B b = new A(); b = new C(); } } class B { } class C extends B { }
-
将子类对象,看作父类对象。因此该变量只能访问父类的成员,对于子类重写的方法,有覆盖的作用在。
6.2 向下转型
-
解决向上转型后,不能访问子类特有的成员。通过向下转型,来将父类对象通过强制转换为子类类型。
-
子类类型 引用名 = (子类类型)父类引用;
public class Test{ public static void main(String[] args) { B b = new A(); System.out.println(b.boy); //向下转型 System.out.println( ((A)b).apple ); A a = (A) b; System.out.println(a.apple); } } class A extends B{ int apple = 1; } class B { int boy = 0; }
6.3 instanceof
对象运算符 instanceof
-
使用:引用名 instanceof 类型
-
作用:如果该引用的对象的运行类型是该类型或该类型的子类型,返回
true
,否则返回false
。
7. 🔹练习
下面代码执行的结果是什么?
public class Test{
public static void main(String[] args) {
B b = new A();
System.out.println(b.sum());
System.out.println(b.sum1());
}
}
class A extends B{
int i = 20;
public int getI() {
return i;
}
}
class B {
int i = 10;
public int sum() {
return getI()+10;
}
public int sum1() {
return i+10;
}
public int getI() {
return i;
}
}
结果:
- 对于b.sum(),首先访问到
class B
中的sum()
方法,在sum()
方法中有一个getI()
方法,对于getI()
的访问,其实在向下引用中,因为class A
中的getI()
是方法重写,该子类方法就重写了父类同名方法的功能,因此调用的是class A
中的getI()
,返回 20 20 20。因此sum()
的结果是 20 + 10 = 30 20+10 = 30 20+10=30。 - 对于b.sum1(),就直接是 10 + 10 = 20 10+10 = 20 10+10=20。因为成员方法没有重写的概念
8. Object类
- Object类是
java.lang
类库中的一个类,所有的类都是直接或间接继承该类。即Object类是所有类的源 - 如果一个类没有使用
extends
关键字,该类就默认为Object类
的子类。
8.1 equels()
该equels()是Object类中所定义的方法,而Object类是所有类的父类,因此任何类都可以直接使用该方法。
-
声明:public boolean equals(Object obj)
-
作用:判断两个引用所指向的是否为同一个对象
-
示例:
-
判断方法 作用 == 可以判断基本类型(值是否相等)、引用类型(是否为同一对象[地址]) equals() 判断引用类型(是否为同一对象[地址]) -
方法重写
如:在
java.lang.String
类中,就将equals()
进行方法重写
如果
String
类型的对象使用equals()
,则判断的是字符串的值。因为子类方法重写了equles()的功能。
8.2 toString()
同样也是java.lang.Object
类中定义的方法,可以直接使用
-
声明:public String toString()
-
默认返回:全类名+@+哈希值的十六进制
-
当输出一个对象时,toString()方法会被默认调用。
-
示例:
-
子类往往会重写
toString()
,在IDEA中,可以使用快捷键:alt+insert
然后选择toString(),默认的是把属性的值输出。
9. final
9.1 简介
- final做为修饰符,表明最终的意思,即不可修改。
9.2 属性
- 如果用final来修饰成员变量,则说明该成员变量为常量。必须有初始化,且不能修改了
- 对其初始化有三种方式:
//方式1:在定义时初始化 class A{ final int a = 10; } //方式2:在构造器中初始化 class B{ final int b; B(int b) { this.b = b; } } //方式3:在代码块中初始化 class C{ final int c; { c = 10; } }
9.3 方法
- 如果用final来修饰成员方法,则表明该方法不能被子类所重写,可提高安全性
9.4 类
- 如果用final来修饰类,则该类不能继承,既不能做为父类,也不能做为子类。
四、抽象类
1.简介
- 使用修饰符abstract所修饰的类
- 抽象类是专门作为父类而创建的,它的作用类似于__模板__
- 目的是根据抽象类的格式来创建子类,再由其子类来创建对象。
- 因此抽象类不能创建实例化对象(new)
2.💫抽象方法
-
使用修饰符abstract所修饰的方法
-
使用:[其他修饰符] abstract 返回类型 方法名(参数列表);
抽象方法没有{}(方法体)
-
示例:
//抽象类B abstract class B { //抽象方法boy() public abstract void boy(); }
-
抽象类中可以没有抽象方法,但是有抽象方法的类必须声明为抽象类
-
抽象类的子类必须实现父类中的所有抽象方法,或者子类也是一个抽象类
示例:
abstract class B { public abstract void boy(); } class A extends B{ //实现父类中的抽象类 public void boy() { } } //子类C也是一个抽象类 abstract class C extends B{ }
-
抽象方法不能使用private、final、static来修饰,因为该关键字修饰的方法都不能重写。
3.其他
-
abstract只能用来修饰类和方法
-
普通类有的成员,抽象类都可以有,因为抽象类的本质还是类
只是不能实例化对象
五、接口
1.简介
- 使用关键字interface来定义接口
-
//接口的定义 [public/无] interface 接口名{ }
- 接口也不能创建实例化对象(new)
2.🎏接口的成员
-
属性,实际上是静态常量
interface A { //属性a,必须初始化 int ONEA = 100; }
对于接口的属性都是public static final修饰的,当省略修饰符,系统会默认。
如:int a=100;
实际上是public static final int a=100;
。 -
抽象方法
interface A { //抽象方法apple() void apple(); }
对于抽象方法,当省略修饰符,系统会默认为public abstract。
如:上例,实际上是
public abstract void apple();
-
在定义接口时,一般都省略属性和抽象方法的修饰符
-
静态方法
interface A { //静态方法 static void go(){ System.out.println("接口A中的静态方法"); } }
接口中的静态方法,不能被子接口和实现类所继承,但可以通过**接口名.静态方法名()**来访问
-
默认方法
interface A { //默认方法 default void to(){ System.out.println("接口A中的默认方法"); } }
接口中的默认方法用
default
修饰符定义,为达到一种在接口中定义与类中普通成员方法相似的效果。 -
接口中的方法都是
public
的,可以在定义时省略。
3.接口的实现
-
利用接口来创建新类的过程是接口的实现
-
接口的实现类似于继承,只不过是使用关键字implenments来实现接口
-
普通类实现接口必须实现接口所有的抽象方法,而抽象类则可不必。
//接口A interface A { void apple(); } //类B实现接口A class B implements A{ //实现抽象方法apple() public void apple() { } } //抽象类C实现接口A abstract class C implements A{ }
-
因为接口中的方法都是
public
,所有在实现抽象方法时,需要显示的使用public
修饰符,实现的方法不能缩小接口中该方法的访问控制范围(和方法重写一样)。
3.1用接口实现类的多重继承
-
java不支持类的多重继承,但是可以利用接口间接的解决这个问题
-
一个类可以实现多个接口,它们间用
,
分隔interface A{} interface B{} class C implements A,B{}
-
接口中的常量、抽象方法和默认方法可以被实现该接口的类所继承。但不能继承接口中的静态方法。
3.2名字冲突
-
一般指一个类实现多个接口,不同接口中相同的默认方法引起的冲突
interface A{ //常量apple int apple = 0; //默认方法 default void to(){} //抽象方法 void play(); } interface B{ //常量apple int apple = 10; //默认方法 default void to(){} //抽象方法 void play(); } class C implements A,B{ //重写to() //这里没有default public void to() { // A.super.to(); } //实现play() public void play() { } void use(){ System.out.println(A.apple); System.out.println(B.apple); } }
-
当实现到两个相同的默认方法时,可以通过重写来解决冲突。而对于抽象方法,因为后续也是要(实现)的,也会达到重写的效果,因此没有冲突。对于接口属性,可以通过接口名.属性来避免冲突。
4.接口的多态
-
用一个类实现一个接口后,该类和该接口的关系类似于继承。
-
如: 二、继承 6。对象的多态
-
可以使用接口的引用来指向子类对象。
public class Test{ public static void main(String[] args) { //父接口引用a指向子类对象 A a = new B(); a.apple(); } } interface A { void apple(); } class B implements A{ //实现抽象方法apple() public void apple() { } //B特有的boy() void boy(){} }
-
也有相同的向下转型
B b = (B)a; b.boy();
5. 接口的继承
-
和类相似,接口也可以继承,使用extends来表明继承关系
-
一个接口可以继承多个父接口,它们间用
,
分隔interface A{ } interface B{ } //接口C继承接口A和B interface C extends A,B{ }
-
接口会继承父接口中的常量、抽象方法和默认方法,但不能继承父接口中的静态方法。
-
接口不能和类通过extends产生关系。
-
如果遇到名字冲突问题
可参考
2.接口的实现
中的解决方法
六、内部类
- 内部类是定义在类中的类,也称为嵌套类,包含内部类的类称为外部类。
- 内部类可以看作外部类的一个成员,与一般类相同,只是定义置于一个类的内部。
1.分类
-
定义在外部类局部位置上,相当于局部变量的地位
a.局部内部类(有类名)
b.匿名内部类(没类名)
-
定义在外部类的成员位置,相当于类成员的地位
a.成员内部类(无static)
b.静态内部类(有static)
2. 局部内部类
-
示例:
class Outer{//外部类 int a = 1; void Outf(){} public void Outf1(){ //一般在方法里定义 class Inner{//内部类 int a = 2; public void Inf(){ //可以直接访问外部类的成员 Outf(); //Outer.this本质就外部类的对象 System.out.println(Outer.this.a);//1 //就近原则 System.out.println(a);//2 } } //使用: Inner inner = new Inner(); inner.Inf(); } }
-
在内部类里可以直接访问外部类的所有成员,对于成员重名问题,默认遵循就近原则,然后如果是访问外部类的成员,可以使用外部类名.this.成员来访问
-
不能有访问修饰符(public、private和protected)。因为它相当于一个局部变量
-
作用域:在定义它的方法或代码块的{}范围中
-
使用:在作用域的{}范围内,先创建对象,再访问。
3.🔑匿名内部类
-
如果某个类的对象只用一次,则可在定义类的同时就创建该类的对象。
-
这种定义类的方法不取名字,所以称为匿名内部类,类名由系统分配。
-
语法:返回的是一个对象的引用
new 类/接口(参数列表) { 类体 };
-
其中类/接口,是匿名内部类所继承的类或实现的接口,类也可以是抽象类,但都只能用一个。
-
示例:
class Outer{//外部类 int a = -1; public void f(){ //定义匿名内部类 //(100)会调用父类的构造器 Father father = new Father(100){ //方法重写 public void show() { System.out.println(Outer.this.getClass()+ " a="+Outer.this.a); System.out.println(this.getClass()+" a="+a); } }; //使用该匿名类的对象 father.show(); } } class Father{ int a; //构造器 public Father(int a) { this.a = a; } public void show(){} }
-
可以发现
father
的运行类型是class Outer$1
这就是系统自动分配的匿名内部类的类名 -
匿名内部类也可以直接访问外部类的所有成员。如果名字相同,遵循就近原则,此时访问外部类成员可以使用外部类名.this.成员
-
作用域: 在定义它的方法或代码块的范围中
-
不能有修饰符,因为相当于一个局部变量。不能定义构造器,因为没有名字。
-
匿名内部类,可以当做实参传递
public class Test{ public static void main(String[] args) { A a = new A(); //直接传递一个实现接口B的匿名内部类 a.Go(new B(){ //实现抽象方法go() public void go() { System.out.println("go"); } }); } } class A{ public void Go(B b){ b.go(); } } interface B{ void go();//抽象方法 }
4. 成员内部类
-
示例:
class Outer{//外部类 int a = 100; public void Outf(){} //成员内部类,定义在外部类成员位置 class Inner{ int a = 9; public void Inf(){ //可直接访问外部类的成员 Outf(); System.out.println(Outer.this.a); } } public void Outf1(){ //使用成员内部类 Inner inner = new Inner(); System.out.println(inner.a); } }
-
在外部类外使用该内部类
public class Test{ public static void main(String[] args) { //创建外部类对象 Outer outer = new Outer(); //创建该外部类对象的内部类对象 Outer.Inner inner =outer.new Inner(); } }
-
成员内部类可以直接可以直接访问外部类的所有成员。如果名字相同,遵循就近原则,此时访问外部类成员可以使用外部类名.this.成员
-
可以添加访问修饰符(public、默认、protected、private),相当于一个成员
5. 静态内部类
-
示例:
class Outer{//外部类 static int a = 100; static int b = 10; public void Outf(){} //静态内部类 static class Inner{ static int b = 9; void Inf(){ //可直接访问外部类的静态成员 System.out.println(a); //如果重名,就近原则,(外部类名.成员) System.out.println(b); System.out.println(Outer.b); //对于外部类的非静态成员的访问 Outer outer = new Outer(); outer.Outf(); } } //静态内部类的使用 public void Outf1(){ //静态内部类的静态成员 System.out.println(Inner.b); //非静态成员,创建对象,再访问 Inner inner = new Inner(); inner.Inf(); } }
-
在外部类外访问该静态内部类
public class Test{ public static void main(String[] args) { //访问静态内部类的静态成员 System.out.println(Outer.Inner.b); //访问静态内部类的非静态成员:先创建,在访问 Outer.Inner inner = new Outer.Inner(); inner.Inf(); } }
-
成员内部类可以直接可以直接访问外部类的静态成员。如果名字相同,遵循就近原则,此时访问外部类静态成员可以使用外部类名.成员
-
可以添加访问修饰符(public、默认、protected、private),相当于一个成员
七、枚举
1.简介
-
对于某一个变量只有几种固定取值时,常声明为枚举类型。如:季节。
-
语法:使用关键字enum声明枚举类型
[修饰符] enum 枚举类型名 { 枚举成员 方法 }
-
枚举成员也称枚举常量或枚举值,不能重名,用
,
分隔,都默认被final public static修饰 -
这里枚举类型名,也是枚举成员的数据类型,所有枚举成员也称为枚举实例或枚举对象
-
枚举是一种特殊的类,会默认继承
java.lang.Enum
类,因此不能再继承其他类,但可以实现接口。 -
非抽象的枚举会默认使用
final
修饰,因此不能派生子类 -
枚举的所有枚举成员必须放在枚举体的第一条语句。
2.不包含方法的枚举
-
因为自动继承
Enum
类,所以可以直接使用Enum
类中的方法 -
方法 功能 public static enumtype[] values() 返回枚举类型的数组,包含所有枚举成员,按声明顺序存储 public static enumtype valueOf(String str) 返回名称为str的枚举成员 public String toString() 返回枚举成员的名称 public final int ordinal() 返回枚举成员的序号(从0开始) public final String name() 返回枚举常量的名称 …… ……
3. ✨包含属性和方法的枚举
-
如果枚举成员,需要有更丰富的内容,这时就可以赋予他们属性和方法
-
示例:
//定义枚举类型Season enum Season{ //声明4个枚举成员 SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTET("冬"); //属性 private String name; //构造器,默认是private Season(String name) { this.name = name; } //重写toString() public String toString() { return name; } void play(){ System.out.println(name+"天就要去玩耍"); } }
-
枚举的构造方法只能是
private
修饰的,可以省略。 -
因此枚举成员必须在定义时就加上
(参数列表)
,如果有无参构造器,则可省略()
🦀🦀观看。
待续~~