Java基础(二)—— 面向对象、抽象类、接口与内部类

本文最后更新于:2021年9月28日 晚上

概览:Java面向对象基本概念学习,还包括抽象类、接口等。

预警!仅用于本人快速自学,不过欢迎指正。

面向对象

Java类成员

  1. 属性:类中的成员变量,不同于局部变量,会有默认值。
  2. 方法:类中的成员函数,也叫行为。
  3. 构造器:用于创建对象,给对象的属性赋值。

构造器

声明构造器的方法:

1
权限 类名(形参){ }
  • 设计类时不显式声明一个构造器则会默认提供一个空参的构造函数。
  • 而一旦显式的声明了一个构造函数,那么默认无参构造函数就不会再提供了。
  • 类的多个构造函数之间是发生重载的。
  • 构造器没有返回类型,也不能写void!

权限修饰

用于修饰对类的成员的访问权限:

修饰符 类内 同一个包 子类 任意
private Y
缺省 Y Y
protected Y Y Y
public Y Y Y Y
  • 类的方法总是可以访问该类的变量,与权限符号无关。

this关键字

  • this可以理解为当前对象或者当前正在创造的对象。
  • this可以用来区分成员变量和局部变量(同名时)。
  • this可以去调用同类的其他方法,也包括构造器。
  • 在构造器中使用this(参数)这样可以显示调用当前类的其他构造函数。
    • 只能用于构造器中。
    • 且只能用于构造器中的第一句。

创建与初始化对象

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

public Person(){
name = "";
age = 0;
}

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

public void ShowInfo(){
System.out.println("name: " + this.name + ", age: "+this.age);
}

public void Learn(String info){
System.out.println(this.name + "正在学习 "+info);
}
}

public class OPPP {

public static void main(String[] args) {
Person p1 = new Person("小明",18);
p1.ShowInfo();
p1.Learn("英语");
}
}

实际过程:

1
2
3
4
5
6
7
8
1. 类加载,同时初始化类中静态的属性
2. 执行静态代码块
3. 分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
4. 调用Student的父类构造器
5. 对Student中的属性进行显示赋值(如果有的话)
6. 执行匿名代码块
7. 执行构造器
8. 返回内存地址

面向对象三大特性——封装

一般将属性私有化,然后提供公有的set、get方法。

面向对象三大特性——继承

利用继承,可以先编写一个具有公共属性的一般类,然后再根据各自特有的属性来编写具有特殊属性的子类。

由继承得到的类是一个子类,被继承的类是父类(超类)。可以理解为:“子类 is a 父类”——student is a person

Java不支持多继承,即一个子类只能有一个父类。但是一个父类可以有多个子类。子类还可以拥有自己的子类。

如果一个类的声明中没有extends,则这个类是默认为是Object的子类。

  • 继承的出现提高了代码的复用性。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。
  • 子类继承了父类,就继承了父类的方法和属性。继承的成员变量或方法的访问权限保持不变。
  • 子类不能直接访问父类中私有的(private)的成员变量和方法
  • 子类可以使用间接父类的方法。

java中的每一个类都是”直接” 或者 “间接”的继承了Object类.所以每一个对象都和Object类有”is a”的关系。

在Object类中,提供了一些方法被子类继承,那么就意味着,在java中,任何一个对象都可以调用这些被继承过来的方法。例如:toString方法、equals方法、getClass方法等。

方法的重写 override

子类可根据需要来对从父类中继承的方法进行改造,覆盖父类的方法。

要求:

  • 重写方法必须和被重写方法具有相同的方法名称、参数列表。

  • 返回类型可以相同,也可以不同,子类重写的返回值不同于父类返回值时子类返回值必须是父类方法返回值的子类型。

  • 重写方法不能使用比被重写方法更严格的访问权限,(private < protected < default < public),即必须大于等于原权限。

  • 静态方法不能够重写,子类可以定义与父类同名的静态方法。

  • 重写和被重写的方法须同时非static的,不能增加static修饰。

  • 子类方法抛出的异常不能大于父类被重写方法的异常

  • 子类不能重写父类中声明为private的方法

  • final关键字修饰的无法再被重写

super关键字

super关键字可用于访问父类中定义的属性

  • 可用其来调用父类中定义的成员方法,包括父类构造器,和被子类重写的方法。

  • 当父类与子类出现同名成员时,可用super关键字进行区分。

  • super与this类似,this代表本类对象的引用,super代表父类的内存空间的标识。

    • 不管是显式还是隐式的父类的构造器,super语句一定要出现在子类构造器中第一行代码。所以this和super不可能同时使用它们调用构造器的功能,因为它们都要出现在第一行代码位置。

instanceof关键字

  • x instanceof A检查对象x是否是类A的对象,返回值为boolean类型。
  • 要求x所属类必须与类A是子类与父类的关系,否则编译报错!

向上转型与强制转型

  1. 父类引用可以指向子类对象,子类引用不能够指向父类对象。
  2. 把子类对象直接赋给父类引用的这种叫做向上转型。Person p = new Student();
  3. 把原本指向子类对象的父类引用再赋给子类引用的叫做向下转型,这里需要强制转型。Person p = new Student(); Student s = (Student)p;
  4. 向上转型既是多态,会丢失子类特有的方法,但是会保存子类重写的方法。

面向对象三大特性——多态

程序的最终状态只有在执行过程中才被决定而非在编译期间就决定了。这对于大型系统来说能提高系统的灵活性和扩展性。

  • 一个类的实际类型是确定的,但是能够指向对象的引用类型却有很多,即这个对象所属类型可能有很多。
1
2
3
4
5
6
7
class Person{};

class Student extends Person{};

Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
  • 即指向对象的引用的类型,可以是该对象实际类型的任意父类型。

例子:

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
class Person{
public String name;
public boolean sex;

public Person(){}

public Person(String name,boolean sex){
this.name = name;
this.sex = sex;
}

public void ShowInfo(){
System.out.println(name + " "+ (sex ? "男":"女"));
}
}

class Student extends Person{
public String id;

public Student(){}

public Student(String name,boolean sex,String id){
super(name,sex);
this.id = id;
}

public void ShowInfo(){
System.out.println(name + " " + (sex ? "男" : "女") + " " + id);
}
}

public class Jicheng {
public static void main(String[] args) {
Student s1 = new Student("小明",true,"01");
Person s2 = new Student("小红",false,"02");
Object s3 = new Student("小黄",true,"03");

s1.ShowInfo();//Student的方法
s2.ShowInfo();//Student的方法
//s3.ShowInfo();//无此方法 编译报错,Object检查发现自己并没有这个方法
}
}

多态注意事项

  1. 多态是方法的多态,属性没有多态!
  2. 重载是编译时多态,重写则是运行时多态!
  3. 多态三大条件:继承、重写、父类引用指向子类对象。

方法绑定

静态绑定:在编译期完成,可以提高代码执行速度。

动态绑定:通过对象调用的方法,采用动态绑定机制。这虽然让我们编程灵活,但是降低了代码的执行速度。这也是JAVA比C/C++速度慢的主要因素之一。JAVA中除了final类、final方法、static方法,所有方法都是JVM在运行期才进行动态绑定的。


static

static修饰的变量称为静态变量

  • 静态变量属于类,可以用类名来访问或者使用对象去访问。

  • 静态变量属于类,被所有类的实例即对象所共享。

  • 在加载类的过程中就会为静态变量分配内存,而实例变量则是在创建对象时分配内存。

static修饰的方法称为静态方法

  • 静态方法属于类,可以使用类名或者对象明去调用。
  • 静态方法不能够访问类中的非静态变量和非静态方法,可以访问静态变量和方法,实际上静态方法没有this和super指针。
  • 非静态方法是可以访问静态变量和静态方法的,毕竟非静态方法加载的时间要落后于静态方法的。
  • 父类的静态方法可以被子类继承,但是不能够被子类重写。即使子类有一个同名的静态方法,也不会发生多态!

代码块和静态代码块

1
2
3
4
5
6
7
8
9
10
public class P{

{
//匿名代码块
}

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
class Code{
//匿名代码块
{
System.out.println("匿名代码块");
}
//静态代码块
static{
System.out.println("静态代码块");
}
public void Cooo(){
System.out.println(this + "普通函数执行");
}
}

public class CodeBlock {
public static void main(String[] args) {
Code c1 = new Code();
Code c2 = new Code();
c1.Cooo();
}
}

/****执行结果***/
静态代码块
匿名代码块
匿名代码块
col.Code@1b6d3586普通函数执行
  • 匿名代码块的工作与构造器类似,因此被用到的概率并不大。
  • 静态代码块的主要作用是给类中的静态成员变量进行初始化赋值操作。

final

修饰类

final修饰的类是不能被继承的,无法有子类,例如String类。

1
public final class Person{}

修饰方法

final修饰的方法可以被继承,但是不能够被重写。例如Object的getClass方法不能重写。

1
2
3
4
5
public class Person{
public final void Info(){

}
}

修饰变量

final修饰的变量表示常量,一经赋值不能修改。

1
2
3
4
5
//修饰参数
public void Info(final int a){
//error final在传参时赋值不能再进行修改
//a = 1;
}

修饰类的成员属性,也是仅有一次赋值机会!

  1. 声明的同时赋值
  2. 构造函数中赋值
  3. 匿名代码块中赋值

修饰类的静态成员属性,仅有一次赋值机会

  1. 声明的同时赋值
  2. 静态代码块赋值

修饰引用变量,已经赋值不能再修改!


抽象类

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类

  • 抽象类中可以没有抽象方法,但是抽象方法的类中一定要声明为抽象类。
1
2
3
public abstract class Animal{
public abstract void Do();//抽象方法 无实现
}
  • 抽象类不能够使用new来创建对象,它是用来让子类继承的!
  • 抽象方法只有声明没有实现,是用来让子类重写的,若不重写,则子类依旧是抽象类。

其他:

  • abstract类有构造器,因为其子类一定会有构造器。
  • final和abstract不能够同时修饰一个方法。因为如果一个非抽象类是一个抽象类的子类,那它必须重写父类的方法,并且给出方法体。

接口

从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。

  • 从接口的实现者角度看,接口定义了可以向外部提供的服务。

  • 从接口的调用者角度看,接口定义了实现者能提供那些服务。

接口与抽象类的区别

  1. 抽象类是类,只是不能new,和普通的类没什么大的区别,而接口和类有很大的不同。
  2. 接口中仅能包含常量和方法的定义!
  3. 类的声明用class,接口的声明用interface
  4. 接口没有构造方法!不能够被实例化!
  5. 抽象类是被用于继承的,且只能是单继承,关键字extends。而接口是被用于实现的,可以被多实现,关键字implements

接口中的方法

接口中的方法默认都是公有抽象方法,由public abstarct来进行修饰,当然这个可以省略。

1
2
3
4
5
public interface MyInter{
public void Func();
//同 void Func();
//同 public abstract void Func();
}

接口中的变量

接口中的变量默认都是公有静态常量,由public static final来进行修饰。

  • 声明的同时就要同时赋值!因为接口中不能够编写静态代码块!
1
2
3
4
5
public interface MyInter{
public static final double PI = 3.14;
// 同 public double PI = 3.14;
// 同 double PI = 3.14;
}

一个类可以实现多个接口

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 interface A {
int M_A = 10;
void Test();
}

public interface B {
int M_B = 20;
void Run();
}

public interface C {
int M_C = 30;
void Work();
}

class MyClass implements A,B,C{
@Override
public void Test() {
System.out.println("A " + A.M_A);
}

@Override
public void Run() {
System.out.println("B " + B.M_B);
}

@Override
public void Work() {
System.out.println("C " + C.M_C);
}
}

public class Main {
public static void main(String[] args) {
A s1 = new MyClass();
B s2 = new MyClass();
C s3 = new MyClass();
MyClass s4 = new MyClass();

s1.Test();//仅能执行接口A提供的方法和Object的方法!
s4.Work();//均可执行!

//强制类型变换
if(s1 instanceof B){
((B)s1).Run();
}
}
}

接口的继承

  • 一个接口可以继承多个接口,而当一个类实现了该接口,就要实现全部的抽象方法!
  • 但是一个接口不能够实现另一个接口。
1
2
3
4
5
6
7
public interface C extends A,B{//可多继承
void CC();
}

public class D implements C{
//实现全部接口的方法!
}

接口的作用

接口最主要的作用就是达到统一访问,即在创建对象时用接口创建

1
接口名 对象名 = new 类名();

这样就可以统一访问,方法名相同,但实现内容不同!

  • 不允许创建一个接口的实例,但允许定义接口类型的引用变量,该引用变量指向了实现了接口的类的实例。