一、类与方法
1. 成员变量
成员变量中[=值]
表示可选内容,定义变量时可以为其赋值,也可以不为其赋值。
成员方法中,权限修饰符可以是 private、public,protected中的任意一个,也可以不写,主要用来控制方法的访问权限。返回值类型用来指定方法返回数据的类型,可以是任何类型,如果方法不需要返回值,则使用void关键字;一个成员方法既可以有参数,也可以没有参数,参数可以是对象,也可以是基本数据类型的变量。如果定义的方法有返回值,则必须使用return关键字返回一个指定类型的数据,并且返回值类型要与方法返回值的类型一致。
2. 成员方法
成员方法的定义
定义格式:
[权限修饰符] [返回值类型]方法名([参数类型 参数名])[throws 异常类型]{
//方法体
return 返回值;
}
成员方法的参数
调用方法时可以给该方法传递一个或多个值,传给方法的值叫作实参,在方法内部,接收实参的变量叫作形参,形参的声明语法与变量的声明语法一样。形参只在方法内部有效。
- 值参数
值参数表明实参与形参之间按值传递,当使用值参数的方法被调用时,编译器为形参分配存储单元,然后将对应的实参的值复制到形参中,由于是值类型的传递方式,所以,在方法中对值类型的形参的修改并不会影响实参。 - 引用参数
如果在给方法传递参数时,参数的类型是数组或者其他引用类型,那么,在方法中对参数的修改会反映到原有的数组或者其他引用类型上,这种类型的方法参数被称之为引用参数。 - 不定长参数
声明方法时,如果有若干个相同类型的参数,可以定义为不定长参数,该类型的参数声明如下∶
权限修饰符 返回值类型 方法名(参数类型... 参数名)
注意∶参数类型和参数名之间是三个点,而不是其他数量或省略号。
构造方法
构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
- 构造方法没有返回类型,也不能定义为void。
- 构造方法的名称要与本类的名称相同。
- 构造方法的主要作用是完成对象的初始化工作,它能把定义对象的参数传给对象成员。构造方法的定义语句代码如下:
class Book{
public Book(){ //(无参)构造方法
}
}
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也将被初始化。如果类中没有明确定义构造方法,则编译器会自动创建一个不带参数的默认构造方法。
除此之外,在类中定义构造方法时,还可以为其添加一个或者多个参数,即有参构造方法,语法如下:
class Book {
public Book(int args){ //有参构造方法
//对成员变量进行初始化
}
}
注意:如果在类中定义的构造方法都是有参构造方法,则编译器不会为类自动生成一个默认的无参构造方法,当试图调用无参构造方法实例化一个对象时,编译器会报错。所以只有在类中没有定义任何构造方法时,编译器才会在该类中自动创建一个不带参数的构造方法。
this
关键字
在Java语言中规定使用this关键字来代表本类对象的引用,this
关键字被隐式地用于引用对象的成员变量和方法。
-this
关键字虽然可以调用成员变量和成员方法,但Java语言中最常规的调用方式是使用对象.成员变量
或对象.成员方法
进行调用。
- 事实上,
this
关键字引用的就是本类的一个对象,在局部变量或方法参数覆盖了成员变量时,就要添加this关键字明确引用的是类成员还是局部变量或方法参数。 this
关键字除了可以调用成员变量或成员方法之外,还可以作为方法的返回值。 例如,在项目中创建一个类文件,在该类中定义Book类型的方法,并通过过this
关键字进行返回。
static 关键字
由static修饰的变量、常量和方法分别被称作静态变量、静态常量和静态方法,也被称作类的静态成员。
- 静态变量
在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。 - 静态方法
用static修饰的方法称为静态方法。调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。 - 静态代码块
在类的成员方法之外,用static修饰代码区域可以称之为静态代码块。定义一块静态代码块,可以完成类的初始化操作,在类声明时就会运行,而构造方法在new
的时候才会运行。
类的主方法
主方法是类的入口点,它指定了程序从何处开始,提供对程序流向的控制。Java编译器通过主方法来执行程序。
主方法的语法如下∶
public static void main(String[] args){
// 方法体
}
在主方法的定义中可以看到主方法具有以下特性∶
- 主方法是静态的,所以如要直接在主方法中调用其他方法,则该方法必须也是静态的。
- 主方法没有返回值。
- 主方法的形参为数组。其中 args[0]~args[n] 分别代表程序的第一个参数到第n+1个参数,可以使用 args.length 获取参数的个数。
二、继承与多态
1. 继承
在java中使用extends 关键字来声明子类继承于某父类,如下:
class 父类 {
}
class 子类 extends 父类 {
}
通过继承,子类只需要编写额外的功能,不再需要重复代码。
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
继承树
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
类似的,如果我们定义一个继承自Person的Teacher,它们的继承树关系如下:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Person │
└───────────┘
▲ ▲
│ │
┌───────────┐ ┌───────────┐
│ Student │ │ Teacher │
└───────────┘ └───────────┘
继承关键字
implements
:Java除了extends,还可以用implements关键字来实现继承。Java中不支持多重继承,但是用implements可以实现多个接口,相当于使得Java具有多继承的特性。public interface if1 { public void method1(); } public interface if2 { public void method2(); } public class if3 implements if1,if2 { }
protected
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private
改为protected
。用protected
修饰的字段可以被子类访问。因此,protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问,super
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。例如:
实际上,这里使用class Student extends Person { public String hello() { return "Hello, " + super.name; } }
super.name
,或者this.name
,或者name
,效果都是一样的。编译器会自动定位到父类的name
字段。
但是,在某些时候,就必须使用super
。如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。final
final
的功能是将类定义为不可继承的,也可以用于修饰方法,被修饰的方法不可被子类重写。
从Java 15开始,允许使用sealed
修饰class
,并通过permits
明确写出能够从该class继承的子类名称。
例如,定义一个Shape
类:
上述public sealed class Shape permits Rect, Circle, Triangle { ... }
Shape
类就是一个sealed
类,它只允许指定的3个类继承它。如果写:
是没问题的,因为public final class Rect extends Shape {...}
Rect
出现在Shape
的permits
列表中。但是,如果定义一个Ellipse
类就会报错
向上和向下转型
向上转型是允许的,即一个引用类型为父类的变量,可以指向子类的实例。
父类类型 变量名=new 子类类型()
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
子类类型 变量名=(子类类型) 父类类型的变量;
如果向下转型的父类变量本就指向子类类型,则可以转型成功,否则失败。失败的时候,Java虚拟机会报ClassCastException
。
为了避免向下转型出错,Java提供了instanceof
操作符,可以先判断一个实例究竟是不是某种类型.。instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null
,那么对任何instanceof
的判断都为false
。
利用instanceof,在向下转型前可以先判断:
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。
public class Main {
public static void main(String[] args) {
Object obj = "hello";
if (obj instanceof String s) {
// 可以直接使用变量s:
System.out.println(s.toUpperCase());
}
}
}
2. 多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。
注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。
加上@Override
可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错。
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
但是@Override
不是必需的。
1. 多态的概念
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态(Polymorphic)。
多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。这种不确定性的方法调用具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
2. 覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String;
- equals():判断两个instance是否逻辑相等;
- hashCode():计算一个instance的哈希值。
在必要的情况下,我们可以覆写Object的这几个方法。在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super
来调用。继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被覆写。如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
。用final
修饰的类不能被继承。对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。