大家好,我是多选参数的程序锅,一个正在 neng 操作系统、学数据结构和算法以及 Java 的硬核菜鸡。下面是本章的内容提纲:
Java 中类的声明形式如下所示,变量的声明和方法的定义意味着只能声明变量、初始化、方法定义等,而不能在方法外进行赋值等操作。
class 类名 {
变量的声明;
方法的定义;
}
★ Java 中的类名推荐使用大驼峰命名法,也就是首字母大写,然后每个单词都大写,比如 ChinaMade。 ”
这种是实例成员变量的声明,声明的变量在类内都可以使用。可以声明的类型包括:整型、浮点型、字符型、逻辑类型、数组、对象、接口等。
★ Java 中的成员变量名字推荐使用小驼峰命名法,也就是首字母小写,后面的每个单词都大写,如 chinaMade。另外一行只声明一个变量。 ”
使用 static 关键字声明类变量,如
class Point {
int w; // 实例变量
static int h; // 类变量
}
类变量和实例变量的区别:
返回值类型 方法名(参数列表) {
......
}
int test() {
......
}
void test() { // 不返回任何数据时,使用 void
......
}
方法定义中声明的变量(包括括号内声明的变量和参数列表中的变量)称为局部变量,局部变量具有以下这些性质:
同样使用 static 关键字声明类方法,如
class Point {
float max(flaot x, float y) { //实例方法
......
}
static float jerry() { // 类方法
......
}
}
类方法与实例方法的区别:
类中的一种特殊方法,创建对象时会调用该类的构造方法。构造方法需要注意以下几点:
class Point{
int h;
int w;
Point() {
this.h = 1;
this.w = 1;
}
Point(int h, int w){
this.h = h;
this.w = w;
}
public void printAll() {
System.out.println(this.h + " " + this.w);
}
}
一个类中可以有多个同名的方法,但是这些方法的参数列表是不一样的,即参数的个数不同或者参数的个数相同,但是对应位置上的参数类型不同。方法的返回类型和参数的名字不参与比较。
该成员可以被任意类中的方法访问
只有同一个类中的成员方法才能访问私有成员,在其他类中不能直接调用。
介于 private 和 public 之间,①同一个包内的所有类的所有方法都能访问该成员;②如果不在同一个包内的类的方法要访问该成员,则该类必须是该成员所在类的子类。
同一个包内的类的方法都能访问该成员,可以省略不写。
权限大小排序:public > protected > default > private
修饰词 | 同一个类 | 同一个包 | 子类(不同包) | 不同包中无继承关系的类 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
default | √ | √ | ||
private | √ |
abstract class Demo { // abstract 类
abstract int min(int x, int y); // abstract 方法
}
abstract 类和方法需要注意以下几点:
就是上面的类成员变量和类方法。
new 构造方法
之后,将会在堆区创建一个对象,相当于类的实例,然后会返回堆区的地址(引用)。可以将这个y地址(引用)赋值给某个引用变量。
// 类的名字 引用变量 = new 构造方法
Point p = new Point();
引用变量指向某个对象实例之后,那么引用变量可以使用 “.” 来访问对象实例中的内容,比如调用方法、访问成员变量等。
p.printAll()
对象数组,这个数组中实际上引用变量的数组。
// 创建了一个对象数组,数组中的每一个元素都是引用变量,都可以指向该类型的对象实例。此时创建之后,每个数组元素并没有指向。
Point[] ps = new Point[10];
ps[0] = new Point(); // ps 对象数组中,索引为 0 的数组元素指向了一个对象实例
this 是指调用该方法或者成员变量的当前的对象实例、构造方法正在创建的对象实例。比如下面这段代码中,当 p 调用 printAll()
方法的时候,该方法中的 this 等同于 p,this 与 p 指向同一个对象实例。Ponit()
这个构造方法中,其实表示将 1、2 赋值给新创建的对象实例。
class Point() {
int h;
int w;
Point() {
this.h = 1;
this.w = 2;
}
void printAll() {
System.out.println(this.h + " " + this.w);
}
}
Point p = new Point();
p.printAll();
this 关键字可以出现在实例方法和构造方法中,但是不能出现在类方法中,这是因为类方法可以通过类名调用,这个时候可能没有对象实例;就算有了,通过类名方式调用的话,this 也不知道指向哪个实例对象。
一个实例方法正在调用类中另一个方法的时可以省略 this 关键字或类名。
面向对象的三要素是封装、继承和多态。
封装就是将事物抽象为类,把对外接口暴露,将实现和内部数据隐藏。
继承是指这样一种能力:它使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。继承创建的新类称为“子类”或者“派生类”;被继承的类称为“基类”、“父类”或“超类”。实现继承一般使用“继承”或者“组合”来实现,Java 就是使用“继承“来实现的,关键字是 extends。在有些 OOP 语言中子类还可以继承多个基类,但是 Java 中只允许继承一个类。子类继承父类的示例代码如下所示:
class 子类名 extends 父类名 {
......
}
class Student extends People {
......
}
继承需要注意以下几点:
当子类声明的成员变量的名字和从父类那边继承来的成员变量的名字相同时,那么子类就会隐藏继承的成员变量。那么,子类自己定义的方法可以操作子类继承的成员变量和子类自己生命的变量,但无法直接访问子类隐藏的成员变量;子类继承的方法操作的是子类继承和隐藏的成员变量,也就是父类自己的成员变量。示例代码如下所示,子类继承 method 方法,那么 method 操作的是父类 A 中 a、b。
public class A {
int a;
int b;
public int method() {
return a*b;
}
}
public SubA extends A {
int a;
int b;
public int methodSub() {
return a + b; // 是指 SubA 中的 a、b
}
}
同成员变量类似,子类继承父类的某个方法之后,子类有权利去重写这个方法。重写是指,子类中重新定义了一个方法,这个方法的返回值类型、名字、参数列表(参数的个数、参数的类型)都跟父类的方法完全相同(返回值类型不相同的话也行,但是需要确保重写之后的类型是父类方法类型的子类型)。一旦重写,那么继承的父类方法将被隐藏起来。重写的示例代码如下所示:
public class Demo {
public float computer (float x, float y) {
return x + y;
}
}
public class SubDemo extends Demo {
public float computer (float x, float y) {
return x * y;
}
}
重写需要注意以下几点:
super 关键字主要是用来操作被隐藏的成员变量/成员方法和构造方法。
super()
,所以都可以不用再写。假如自己要调用父类的某个构造方法的话,那么一定要把调用放在构造方法的第一句。
因为,子类的构造方法在默认情况下会调用父类不带参数的构造方法,因此在实现类的时候,如果实现了带参数的构造方法,那么一定要添加一个无参数的构造方法,以防子类出错。使用子类的构造方法创建一个子类的对象时,子类和父类的成员变量都分配了内存空间,其实通过上述 super 可以看到,父类的构造方法也被调用了,因此相当父类也是被创建了的。
下面来阐述一下 instanceof 运算符,这是一个二元运算符,左边是一个引用变量,右边是一个类,主要是判断左边引用变量所指的实例对象是否是右边类的一个实例对象,如下所示,输出为 True。这是因为 sp 指向的还是 new Student() 出来的实例对象。
Student stu = new Student()
SchoolPeople sp = stu;
if (sp instanceof Student) {
System.out.println("True!");
} else {
System.out.println("False!");
}
================================
True
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它子对象的特性以不同的方式运作。简单的说,就是把子类的对象实例赋值给父类的引用变量,父类的引用变量就可以访问子类的成员变量或者方法,因为赋值的子类对象实例不同,因此呈现多态。将子类的引用变量赋值给父类引用之后,父类引用变量称为子类引用变量的上转型对象,如下所示,a 被称为 b 的上转型对象。
// Tiger 是 Animal 的子类
Animal a;
Tiger b = new Tiger();
a = b;
// 上述等价于:Animal a = new Tiger();
那么上转型对象需要注意以下几点:
★ 这几点总的来说就是上转型对象中指向的是子类对象实例,但是引用变量的类型还是父类的,所以只能访问父类中有的内容,比如子类继承或隐藏的成员变量,子类继承的方法或子类重写的方法,这些父类中都有。另外,为什么静态方法需要那样呢?因为静态方法可以通过类名调用的,静态方法是属于这个类的方法,所以当你使用上转型对象调用静态方法时相当于父类去调用静态方法,那调用的自然是父类的静态方法。 ”
那么怎么理解多态呢?个人理解就是,相同方法,在父子类中展现不同行为特征的能力,这个主要是因为赋值给父类引用变量的子类对象实例不同而呈现不同。那么怎么可以让相同方法的方法被调用时可以不同呢?那就是让子类重写父类中的某个方法(static 方法除外)。 如下所示,那么当把 Teacher 的实例对象传递给 sp 的时候,sp.work()
调用的是 Teacher 这个类中的 work()
方法,输出的是 Teacher Work
;当把 Student 的实例对象传递给 sp 的时候,sp.work()
调用的是 Student 这个类中的 work()
方法,输出的是 Student Work
,也就呈现多态的特性。需要注意的是,sp.joinClub()
是错误的,因为 SchoolPeople 中并没有这个方法,假如有一次传给 sp 的是 Teacher 实例对象,但是 Teacher 类中并没有 joinClub()
这个方法,那这样调用的话就崩溃了。
public class SchoolPeople {
public void work() {
System.out.println("SchoolPeople Work");
}
}
public class Student extends SchoolPeople {
public void work() {
System.out.println("Student Work");
}
public void joinClub() {
System.out.println("Join Club");
}
}
public class Teacher extends SchoolPeople {
public void work() {
System.out.println("Teacher Work");
}
public void attendMeeting() {
System.out.println("Attend Meeting");
}
}
public class MainClass {
public void manager(SchoolPeople sp) {
sp.work();
}
}
★
其实还有一个向下转型,就是把父类引用变量指向的实例对象转化为或者赋值给相应子类的引用变量,这个时候是需要强制类型转换的。比如下面的代码:
// Student 是 SchoolPeople 的子类
Student stu = new Student();
SchoolPeople sp = stu
Student stu1 = (Student)sp;
当然需要注意,当向下转型时,父类引用变量指向的实例对象的对象类型要与赋值的子类引用变量的类型是一致的,下面这样的代码就是不对了的:
// Cat、Dog 是 Pet 的子类
Cat cat = new Cat();
Pet pet = cat;
Dog dog = (Dog)pet; // 这就错了
实名内部类是指在类中再嵌套一个类的定义。内部类的修饰词可以是 public、protected、default、private;并且内部类可以访问外嵌类的成员变量和方法。这边我将内部类分为两类:非静态实名内部类和静态实名内部类。
非静态实名内部类其实也就是没有 static 关键字修饰的内部类,那么这个类类似于一个成员变量。在内部类中需要注意以下几点:
外部类名.内部类名.成员名
;对于非静态成员,引用变量(指向内部类的对象实例).成员名
静态实名内部类也就是有 static 关键字修饰的内部类,类似于类成员变量。在静态实名内部类中,需要注意以下几点:
外部类名.内部类名.成员名
;对于非静态成员,引用变量(指向内部类的对象实例).成员名
匿名内部类是没有类名,在 Java 中经常被用到。匿名内部类如下所示,表示定义了一个没有名字的子类,并同时创建该子类的一个实例对象。
new 父类名(父类构造方法参数列表) {
子类自己的实现;
}
使用 interface 来定义一个接口,如下所示
interface Printable {
int MAX = 100;
void add();
float sum(float x, float y);
}
public final static
,为了方便这三个修饰符在接口中都可以省略不写。public abstract
,为了方便这两个修饰符在接口中都可以省略不写。一个类使用 implements 关键字表示实现了某个接口,如
class Demo implements Printable {}
class Demo implements Printable, Addable {}
首先,阐述一下什么是接口变量?接口变量也就是用接口类型声明的变量。可以将实现了该接口的类的对象实例的引用赋值给该接口变量。如下
interface Com {
......
}
class ImpleCom implements Com {
......
}
Com com = new ImpleCom();
ImpleCom obj = new ImpleCom();
Com com = obj;
接口回调就是指可以通过接口变量调用被类实现的接口的方法,但是类中其他的非接口方法是无法通过接口变量调用的。
Java 中的 package 类似于 C 语言中的头文件的概念。在 Java 中将相似功能的类都放在同一个包中,该包的包名就是该类所在的目录路径,比如这个类所在的目录路径是 com/edu/cn
,那么包名就应该是 com.edu.cn
。
Java 使用 package 关键字显式得将一个类放入到某个包中,如在一个类的开头使用 package com.edu.cn
则表示将这个类放到 com.edu.cn
这个包中,com.edu.cn
其实是一个逻辑路径,表示该类位于逻辑路径 com/edu/cn
中,为了 import 的正确性,还需确保这个逻辑路径要跟类所在的物理路径一样。那么,当一个类有了包名之后,那么这个时候这个类的全称应该是 包名.类名
。
package com.edu.cn
public class DemoClass {
......
}
当你想要导入一个包中的某个类的时候,使用 import 关键字,import ******
。比如该类存放在物理路径 com/edu/cn
中,该类所处的逻辑路径也应该是 com/edu/cm
,即类所处包名应该是 com.edu.cn
,那么则使用 import com.edu.cn.类名
即可正确导入该类。这个时候必须确保 import 导入的类所在的物理路径和类的逻辑路径是相同,这是因为 import 将会根据包名去相应的物理路径中寻找这个类(全称),比如 import com.edu.cn.A
,则会去 com/edu/cn
目录中寻找 com.edu.cn.A
这个类文件。但是,假如包名和类所处的物理路径不一致,将会无法正确加载。
下面我们来看一个例子,有两个类 DemoClass1 和 DemoClass2,这两个类所在的 java 文件位于同一物理路径下,DemoClass2 会用到 DemoClass1,那么 DemoClass2 会去 classpath 设置的环境变量中寻找叫 DemoClass1 的类,但是当前目录下的类不叫 DemoClass1,而是叫 com.edu.cn.DemoClass1,所以将无法正确加载。
// DemoClass1 类
package com.edu.cn
public class DemoClass1 {
......
}
// DemoClass2 类
public class DemoClass2 {
public static void main(String args[]) {
DemoClass1 demo = new DemoClass1();
}
}
可以将 DemoClass2 也 package 到 com.edu.cn 中,那么可以正确加载
package com.edu.cn
public class DemoClass2 {
public static void main(String args[]) {
DemoClass1 demo = new DemoClass1();
}
}
Java 中有两种包的导入机制:
import java.io.File
。单类型导入是仅仅导入一个 public 类或者接口。import java.io.*
。按需导入不是导入一个包下的所有类,而是导入当前类需要使用的类。单类型导入和按需类型导入对类文件的定位算法是不一样的。Java 编译器会从启动目录,扩展目录和用户类路径下去定位需要导入的类,而这些目录/路径仅仅给出了类的顶层目录。编译器的类文件定位方法大致可以理解为如下公式:顶层路径名/包名/文件名.class == 绝对路径
。