Java语言基本元素
类和对象
概念
-
类(Class)和对象(Object)是面向对象的核心概念。
类是对一类事物的描述,是抽象的、概念上的定义
对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
-
“万事万物皆对象”
Field = 属性 = 成员变量
Method = (成员)方法 = 函数
创建类的对象 = 类的实例化 = 实例化类
类
现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理, Java代码世界是由诸多个不同功能的类构成的。
现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、 … 那么,Java中用类class来描述事物也是如此。常见的类的成员有:
- 属 性:对应类中的成员变量
- 行 为:对应类中的成员方法
图示属性和行为
图示类成员的构成(完成版)
类的语法格式
修饰符 class 类名 {
属性声明;
方法声明;
}
说明: 修饰符public:类可以被任意访问
类的正文要用{ }括起来
举例:
public class Person{
private int age ; //声明私有变量 age
public void showAge(int i) { //声明方法showAge( )
age = i;
}
}
如何创建Java自定义类
-
定义类(考虑修饰符、类名)
-
编写类的属性(考虑修饰符、属性类型、属性名、 初始化值)
-
编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
对象
对象的创建和使用
创建
类名 对象名 = new 类名();
使用
访问对象成员(包括属性和方法)
对象名.对象成员
注意
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
- 意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
对象的内存解析
对象的产生
Person p1=new Person();
对象的生命周期
类对象的赋值相当于赋值的是引用(保存对象的地址)
Person p1=new Person(); Person p2=p1;
内存解析
内存解析相关概念
堆(Heap) , 此内存区域的唯一目的就是存放对象实例, 几乎所有的对象实例都在这里分配内存。 这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
通常所说的栈(Stack) , 是指虚拟机栈。 虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、 byte、char 、 short 、 int 、 float 、 long 、double) 、 对象引用(reference类型,它不等同于对象本身, 是对象在堆内存的首地址) 。 方法执行完, 自动释放。
方法区(Method Area) , 用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。
内存解析实例
匿名对象
不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
例如:
new Person().shout();
什么情况下使用匿名对象?
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
类的成员
属性(变量)
语法格式
修饰符 数据类型 属性名 = 初始化值 ;
说明1:修饰符
-
常用的权限修饰符有: private、缺省、 protected、 public
-
其他修饰符: static、 final (暂不考虑)
说明2:数据类型
任何基本数据类型(如int、 Boolean) 或 任何引用数据类型。
说明3:属性名
属于标识符,符合命名规则和规范即可
变量的分类
成员变量:在方法体外,类体内声明的变量称为成员变量。
局部变量:在方法体内部声明的变量称为局部变量。
图示变量的分类
成员变量(属性)和局部变量的区别?
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、 public、 static、 final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内 栈 | 栈空间 |
解析
1.相同点:
1.1 定义变量的格式:数据类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都有其对应的作用域
2.不同点:
2.1 在类中声明的位置的不同
-
成员变量:直接定义在类的一对{}内
-
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
2.2 关于权限修饰符的不同
-
成员变量:可以在声明属性时,指明其权限,使用权限修饰符。
-
常用的权限修饰符:private、public、缺省、protected —>封装性
-
目前,大家声明属性时,都使用缺省就可以了。
-
局部变量:不可以使用权限修饰符。
2.3 默认初始化值的情况:
-
成员变量:类的属性,根据其类型,都有默认初始化值。- 默认初始化赋值表
-
局部变量:没有默认初始化值。意味着,我们在调用局部变量之前,一定要显式赋值。
-
特别地:形参在调用时,我们赋值即可。
2.4 在内存中加载的位置:
-
成员变量:加载到堆空间中 (非static)
-
局部变量:加载到栈空间
变量的默认初始化赋值
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了
基本数据类型之外的变量类型都是引用类型,如上面的Person及前面讲过的数组。
成员变量类型 | 初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0 或写为:’\u0000’(表现为空) |
boolean | false |
引用类型 | null |
属性赋值的先后顺序
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过"对象.方法" 或 "对象.属性"的方式,赋值
⑤在代码块中赋值
以上操作的先后顺序:① - ②/⑤(根据先后顺序判断) - ③ - ④
方法
什么是方法(method、函数)
- 方法是类或对象行为特征的抽象,用来完成某个功能操作(描述类应该具有的功能)。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里。
方法的声明格式
修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, ….){
方法体程序代码
return 返回值;
}
注意:
- 修饰符: public,缺省,private, protected等
- 返回值类型:
- 没有返回值: void。
- 有返回值,声明出返回值的类型。与方法体中“return 返回值” 搭配使用
-
方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
形参列表:可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开
返回值:方法在执行完毕后返还给调用它的程序的数据。 -
方法中,不可以定义方法。
-
特殊的:方法A中又调用了方法A:递归方法。
return关键字的使用:
使用范围:使用在方法体中
作用:① 结束方法
② 针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据。
注意点:return关键字后面不可以声明执行语句。
如何理解方法返回值类型为void的情况 ?
返回值类型: 有返回值 vs 没有返回值
-
如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。
-
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。
方法的分类
分类 | 有返回值 | 无返回值 |
---|---|---|
无形参 | void 方法名(){} | 返回值的类型 方法名(){} |
有形参 | 有形参 void 方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
方法的调用
方法通过方法名被调用,且只有被调用才会执行。
方法调用的过程分析
注意事项
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性, 不可以在方法内部定义方法。
方法的重载
概念
重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点
与返回值类型无关,只看参数列表,且参数列表必须不同。 (参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
“两同一不同”:
- 同一个类、相同方法名
-
参数列表不同:参数个数不同,参数类型不同
示例
//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}
判断是否为重载
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
方法的重写
主要用于继承。继承
在子类中可以根据需要对从父类中继承来的方法进行改造, 也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、 参数列表
-
返回值类型:
-
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
-
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
-
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
-
-
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
- 子类方法抛出的异常不能大于父类被重写方法的异常
注意:
子类与父类中同名同参数的方法必须同时声明为非static
的(即为重写),或者同时声明为static
的(不是重写) 。因为static方法是属于类的,子类无法覆盖父类的方法。
示例
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
分析
Person p1=new Person();
//调用Person类的getInfo()方法
p1.getInfo();
Student s1=new Student();
//调用Student类的getInfo()方法
s1.getInfo();
这是一种“多态性”:同名的方法,用不同的对象来区分调用的是哪一个方法。
再谈方法的重载与重写
详情见多态性的方法的重载与重写 方法的重载与重写与多态性(方法的重载是多态性的一种体现?NO)
方法中可变个数的形参
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前: 采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK 5.0开始: 采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
注意事项
-
声明格式: 方法名(参数的类型名 …参数名)
-
可变参数:方法参数部分指定类型的参数个数是可变多个: 0个, 1个或多个
-
可变个数形参的方法与同名的方法之间,彼此构成重载
-
可变参数方法的使用与方法参数部分使用数组是一致的
-
方法的参数部分有可变形参,需要放在形参声明的最后
-
在一个方法的形参位置,最多只能声明一个可变个数形参
方法参数的值传递机制
形参:方法定义时,声明的小括号内的参数
实参: 方法调用时,实际传递给形参的数据
Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种: 值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
参数传递解析
基本数据类型的参数传递
形参不会修改堆空间实参的具体值,因为形参此时存储的是实参的数据值
public static void main(String[] args) {
int x = 5;
System.out.println("修改之前x = " + x);// 5
// x是实参
change(x);
System.out.println("修改之后x = " + x);// 5
}
public static void change(int x) {
System.out.println("change:修改之前x = " + x);//5
x = 3;
System.out.println("change:修改之后x = " + x);//3
}
引用数据类型的参数传递
形参会修改堆空间实参的具体值,因为形参此时存储的是实参的地址引用.
public static void main(String[] args) {
Person obj = new Person();
obj.age = 5;
System.out.println("修改之前age = " + obj.age);// 5
// x是实参
change(obj);
System.out.println("修改之后age = " + obj.age);// 3
}
public static void change(Person obj) {
System.out.println("change:修改之前age = " + obj.age);//5
obj.age = 3;
System.out.println("change:修改之后age = " + obj.age);//3
}
//其中Person类定义为:
class Person{
int age;
}
递归方法
递归方法:一个方法体内调用它自身。
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
//计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
main方法
main方法的特点
-
main()方法作为程序的入口
-
main()方法也是一个普通的静态方法
-
main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
命令行参数用法举例
public class CommandPara { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] = " + args[i]); } } }
//运行程序CommandPara.java
java CommandPara “Tom" “Jerry" “Shkstart"
输出结果:
输出结果:
args[0] = Tom args[1] = Jerry args[2] = Shkstart
理解main方法的语法
- 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public。又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
- 因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
构造器(构造方法)
构造器的特征
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、 final、 synchronized、 abstract、 native修饰,不能有return语句返回值
构造器的作用
1. 创建对象;
2. 初始化对象的信息
如: Order o = new Order(); Person p = new Person(“Peter”,15);
如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人” 的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
语法格式
修饰符 类名 (参数列表) {
初始化语句;
}
构造器的分类
根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
构造器的注意事项
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器, 则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
- 一个类中,至少会有一个构造器。
构造器重载
构造器重载,参数列表必须不同
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象
构造器重载举例:
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
代码块
代码块的作用
用来初始化类、对象
代码块的分类
代码块如果有修饰的话,只能使用static.
一个类中代码块若有修饰符, 则只能被static修饰, 称为静态代码块(static block), 没有使用static修饰的, 为非静态代码块。
静态代码块 vs 非静态代码块
静态代码块:用static 修饰的代码块
- 内部可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
- 作用:初始化类的信息
非静态代码块:没有static修饰的代码块
- 内部可以有输出语句。
- 可以对类的属性、 类的声明进行初始化操作。
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
- 若有多个非静态的代码块, 那么按照从上到下的顺序依次执行。
- 每次创建对象的时候, 都会执行一次(随着对象的创建而执行)。 且先于构造器执行。
- 作用:可以在创建对象时,对对象的属性等进行初始化
使用示例
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person{
//属性
String name;
int age;
static String desc = "我是一个人";
//构造器
public Person(){}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代码块
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
//调用非静态结构
age = 1;
eat();
//调用静态结构
desc = "我是一个爱学习的人1";
info();
}
//static的代码块
static{
System.out.println("hello,static block-2");
}
static{
System.out.println("hello,static block-1");
//调用静态结构
desc = "我是一个爱学习的人";
info();
//不可以调用非静态结构
// eat();
// name = "Tom";
}
//方法
public void eat(){
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info(){
System.out.println("我是一个快乐的人!");
}
}
内部类
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
什么情况下使用内部类?
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
内部类分类
成员内部类(静态、非静态)
一方面,作为外部类的成员:
-
调用外部类的结构
-
可以被static修饰, 但此时就不能再使用外层类的非static的成员变量
-
可以被4种不同的权限修饰
另一方面,作为一个类:
-
类内可以定义属性、方法、构造器等
-
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
-
可以被abstract修饰,因此可以被其它的内部类继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
局部内部类(方法内、代码块内、构造器内)
如何声明内部类?
class 外部类{
//方法中使用局部内部类
方法(){
class 局部内部类{
}
}
//代码块中使用局部内部类
{
class 局部内部类{
}
}
}
如何使用局部内部类
-
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
-
但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型、
public class InnerClassTest { //开发中很少见 public void method(){ //局部内部类 class AA{ } } //返回一个实现了Comparable接口的类的对象 public Comparable getComparable(){ //创建一个实现了Comparable接口的类:局部内部类 //方式一: // class MyComparable implements Comparable{ // // @Override // public int compareTo(Object o) { // return 0; // } // // } // // return new MyComparable(); //方式二: return new Comparable(){ @Override public int compareTo(Object o) { return 0; } }; } }
局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。 由局部内部类和局部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
- 局部内部类不能使用static修饰,因此也不能包含静态成员
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表) |实现接口(){
//匿名内部类的类体部分
}
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
注意事项
- 非static的成员内部类中的成员不能声明为static的, 只有在外部类或static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员, 需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员, 包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时, 可以考虑内部类声明为静态的
使用示例
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
// Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
System.out.println();
bird.display("黄鹂");
}
}
class Person{
String name = "小明";
int age;
public void eat(){
System.out.println("人:吃饭");
}
//静态成员内部类
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是条狗");
}
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public Bird(){
}
public void sing(){
System.out.println("我是一只小小鸟");
Person.this.eat();//调用外部类的非静态属性
eat();
System.out.println(age);
}
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
}
public void method(){
//局部内部类
class AA{}
}
{
//局部内部类
class BB{}
}
public Person(){
//局部内部类
class CC{}
}
}