1.this关键字
构造器是创建Java对象的途径,是不是说构造器完全负责创建Java对象?
答:不是!构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但这个对象并不是完全由构造器负责创建的。实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了。也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,从而让外部程序可以访问该对象。—《疯狂Java讲义》 |
---|
一、对象创建的过程和this的本质:
构造方法是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回该类的对象,但这个对象并不是完全由构造器负责创建。创建一个对象分为如下四步:
- 分配对象空间,并将对象成员变量初始化(默认初始化)。
- 执行属性值的显示初始化(private String name=“刘瘦瘦”)。
- 执行构造方法。
- 返回对象的地址给相关变量。
this的本质就是“创建好的对象的地址”(对象的默认引用)!由于在构造方法调用前,对象已经创建。因此在构造方法中也可以使用this代表当前对象。
二、this关键字的意义
根据this出现位置的不同,this作为对象的默认引用有两种情形:
- 构造器中引用该构造器正在初始化的对象;
- 在方法中引用调用该方法的对象。
三、this关键字的作用
1.当成员变量和局部变量重名,可以用this区分。
public class This {
private String name;
private int age;
public This(String name,int age){
//成员变量名和局部变量重名,可以用关键字this区分
this.name=name;
this.age=age;
}
}
2.this关键字调用重载的构造方法,避免相同的初始化代码。但是,只能在构造方法中用,并且必须位于构造方法的第一句。
class TestThis {
private String name;
private int age;
public TestThis(){
System.out.println("就是这么牛逼");
}
public TestThis(String name){
this();
this.name=name;
}
public TestThis(String name,int age){
this(name);
this.age=age;
}
}
public class ThisTest2{
public static void main(String[] args) {
TestThis tt= new TestThis("刘瘦瘦");//会输出“就是这么牛逼”
}
}
3.当this作为对象的默认引用使用时,程序可以像访问普通引用变量一样来访问这个this引用,甚至可以把this当成普通方法的返回值。
package com.liuyongbin.thisTest;
public class ReturnThis{
private int age;
public ReturnThis grow(){
age++;
return this;//返回调用该方法的对象
}
public static void main(String[] args) {
ReturnThis rt=new ReturnThis();
//可以连续调用一个方法
rt.grow().grow().grow();
System.out.println("rt的age Field值是:"+rt.age);//rt的age Field值是:3
System.out.println(rt.grow());//com.liuyongbin.thisTest.ReturnThis@15db9742
System.out.println(rt);//com.liuyongbin.thisTest.ReturnThis@15db9742
}
}
2.package
package关键字的使用
- 为了更好的实现项目中类的管理,提供了包的概念。
- 使用package声明类或接口所属的包,声明在源文件的首行。
- 包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz 包通常用小写单词标识。通常使用所在公司域名的倒置:com.liushoushou.xxx)、见名知意。
- 每"."一次,就代表一层文件目录。
- 同一个包下,不能命名同名的接口、类。
- 不同的包下,可以命名同名的接口、类。
包的作用:
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 包可以包含类和子包,划分项目层次,便于管理
- 解决类名冲突的问题以及控制访问权限
JDK中主要的包介绍
- java.lang—包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。
- java.net—包含执行与网络相关的操作的类或接口。
- java.io—包含能提供多种输入/输出功能的类。
- java.util—包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text—包含了一些java格式化相关的类。
- java.sql—包含了java进行JDBC数据库编程的相关类/接口。
3.import与import static
import的作用
- 为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。
import的语法格式
- import 包名. 类名;
注意
- 在源文件中显示的使用import结构导入指定包下的类、接口
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可
- 可以使用“xxx.*”的方式,表示可以导入xxx包下的所有结构
- 如果使用的类或接口是本包下定义的,则可以省略import结构
- 如果使用的类或接口是java.lang包下定义的,则可以省略import结构(Java默认为所有源文件导入java.lang包下的所有类)
- 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示
- 使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显示导入
import static 的作用
- 导入指定类或接口中的静态结构:属性或方法
import static 的语法格式
- 语法格式1:import static 包名.类名.field|methodName;//包层次下指定的类的指定的静态Field、方法
- 语法格式2:import static 包名.类名.*;//包层次下指定的类的全部静态Field、方法
一句话归纳import和import statiC的作用:使用import可以省略写包名; 而使用import static则可以连类名都省略。
4.super关键字
super关键字的使用
- 在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
super调用属性和方法
- 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用 父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的 使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的 使用"super.方法"的方式,表明调用的是父类中被重写的方法。
class BaseClass{
public int a=5;
}
public class SubClass extends BaseClass{
public int a=7;
public void accessOwner(){
System.out.println(a);
}
public void accessBase(){
//通过super来限定访问从父类继承得到的a field
System.out.println(super.a);
}
public static void main(String[] args) {
SubClass sc=new SubClass();
//输出5
sc.accessBase();
//输出7
sc.accessOwner();
}
/*
* 分析;
* 上面程序的BaseClass和SubClass中都定义了名为a的实例Field,则SubClass的a实例Field将会隐藏BaseClass的a实例Field。
*当系统创建了subClass对象时,实际上会为SubClass对象分配两块内存,一块用于存储在SubClass类中定义的a Field,
*一块用户存储从BaseClass类继承得到的a Field。
* 程序中程序访问super.a时,此时使用super限定访问该实例从父类继承得到的a Field,而不是在当前类中定义的a Field。
* 如果子类里没有声明和父类同名的Field,那么子类实例方法中访问该Field时,
*则无需显示使用super或父类名作为调用者。如果在某个方法中访问名为a的Field,但没有显示指定调用者,则系统查找a的顺序为:
* (1)查找该方法中是否有名为a的局部变量;
* (2)查找当前类中是否包含名为a的Field;
* (3)查找a的直接父类中是否包含名为a的Field,依次上溯a的所有父类,
* 直到java.lang.Object类,如果最终不能找到名为a的Field,则系统出现编译错误。
* 如果被覆盖的是类Field,在子类的方法中则可以通过父类名作为调用者来访问被覆盖的类Field
*/
}
注意:
当程序创建一个子类对象时,系统不仅会给该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,当前类中定义2个实例变量,那么这个Java对象将会保存2+3+2个实例变量。如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变。注意不是完全覆盖,因此系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间,所以会出现如下特殊的情形 |
---|
class Parent{
public String str="全世界拼成首诗";
}
class Derived extends Parent{
//定义一个私有的str来隐藏父类的tag实例变量
private String str="我爱你当做最后一行";
}
public class HideTest{
public static void main(String[] args) {
Derived d = new Derived();
//程序不访问d的私有变量tag所以下面语句将引起编译错误
//System.out.println(d.str);
//将d变量显示地向上转型为Parent后,即可访问str实例变量
System.out.println(((Parent)d).str);//全世界拼成首诗
}
}
super调用父类构造器
- 子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,类似于前面所介绍的一个构造器调用另一个构造器。
子类调用父类构造器的几种情况
- 子类构造器执行体的第一行使用super显示调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
- 子类构造器执行体的第一行代码使用this显示调用本类中重载的构造器,系统将根据this调用里传入的实参表调用本类中的另一个构造器。执行本类中另一个构造器时会调用父类构造器。
- 子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
不管上面哪种情况,当调用子类构造器来初始化对象时,父类构造器总会在子类构造器之前执行:不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器…以此类推,创建任何Java对象,最先执行的总是Java.lang.Object类的构造器。
对于下图所示的继承树:如果创建ClassB的对象,系统将先执行java.lang.Object类的构造器,在执行ClassA类的构造器,然后才执行ClassB类的构造器,这个执行过程还是最基本的情况。如果ClassB显示调用ClassA的构造器,而该构造器又调用了ClassA类中重载的构造器,则会看的ClassA两个构造器先后执行的情形(看下面的代码演示)。
/*
* 测试继承树
*/
class Creature{
public Creature(){
//super();调用object的空构造器
System.out.println("Creature无参的构造器");
}
}
class Animal extends Creature{
public Animal(String name){
//super();//调用Creature的空构造器
System.out.println("Animal带一个参数的构造器,"+"该动物的name为"+name);
}
public Animal(String name,int age){
//使用this调用一个重载的构造器
this(name);
System.out.println("Animal带两个参数的构造器,"+"其age为"+age);
}
}
public class Wolf extends Animal {
public Wolf(){
//显示调用父类有两个参数的构造器
super("灰太狼",3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
//输出结果
/*
* Creature无参的构造器
Animal带一个参数的构造器,该动物的name为灰太狼
Animal带两个参数的构造器,其age为3
Wolf无参数的构造器
*/
}
}
从上面运行程序来看,创建任何对象总是从该类所在继承数最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类构造器。如果某个父类通过this调用了同类中重载的构造器,就会依次执行此父类的多个构造器。
为什么我们创建Java对象时从未感觉到java.lang.Object类的构造器被调用过?
答:因为自定义的类从未显式调用java.lang.Object类的构造器,即使显式调用,java.lang.Object类也只有一个默认的构造器可被调用。当系统执行java.lang.Object类的默认构造器时,该构造器的执行体并未输出任何内容,所以你感觉不到调用过java.lang.Object类的构造器。 |
---|
this和super的区别
- 在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成
- 使用super调用父类构造器和使用this调用同一个类的重载的构造器都必须在构造器的首行(也就说明this和super不会同时出现)
- 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)否则会造成死循环。这也就说明,在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器。(子类中所有的构造函数默认都会访问父类中的空参数的构造函数。当然,如果子类中指定了访问父类带参数的构造函数,就不会访问父类默认的构造函数)