一、this
this是代表当前对象的引用
我们会发现在构造函数的内部,我们可以使用this关键字,构造函数是用来构造对象的,对象还没有构造好,我们就使用了this,那this还代表当前对象吗?当然不是,this代表的是当前对象的引用。
一个对象的产生分为几步? ①为对象分配内存 ②调用合适的构造方法。(构造方法不止一个)那么既然对象产生分两步,必须把两步骤完成,第二步走完之后才会产生对象,而产生对象的过程中就用this,this就不能代表当前对象,因为只完成了第一步为对象分配内存,那么此时this就代表当前对象
public Student(String myName, int age) {
this.myName = myName;
this.age = age;
}
用法
- this(); 调用本类其他的构造方法,且必须放在第一行
- this.data; 访问当前类当中对属性
- this.func(); 调用本类的其他的成员方法
class Student {
private String myName ;
private int age;
//默认构造函数 构造对象
public Student( ) {
this("Tom",12); //-----调用本类其它构造方法 必须放在第一行进行显示
}
public Student(String myName, int age) {
this.myName = myName;
this.age = age;
}
public void func1 () {
this.show();//------------------调用本类的其他成员方法
System.out.println("func1 ( )");
System.out.println(this.age);//----------访问当前类当中的字段
}
public void show() {
System.out.println("我的名字是" + this.myName + ", 今年" + this.age + "岁");
}
public void setMyName ( String myName) {
this.myName = myName; //---------- 正确this引用,表示调用该方法的对象
} //或者当前对象的引用
public String getMyName ( ) {
return this.myName;
}
}
public class TestDemo {
public static void main(String[] args) {
Student student = new Student( );
student.show(); //我叫Tom, 今年12岁
student.func1();
}
}
二、super
super是代表父类对象的引用
为什么子类对象初始化时,都需要调用父类中的构造函数?为什么要在子类构造函数的第一行加入这个super()
因为子类继承父类,会继承到父类中的数据,所以必须要看父类是如何对自己的数据进行初始化的。所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。
- super(); 调用父类的构造方法也是必须放到第一 行
- super.data(); 访问父类的属性
- super.func(); 访问父类的成员方法
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println("我是一只小动物");
System.out.println(this.name + "正在吃" + food);
}
}
public class Bird extends Animal {
public Bird(String name) {
super(name); //调用父类的构造方法
}
@Override
public void eat(String food) {
super.eat(food); //调用父类的方法
System.out.println(super.name); //调用父类的属性
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
总结
-
super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
-
this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
-
调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用 super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
-
super() 和 this() 均需放在构造方法内第一行。且不能同时出现在一个构造函数里
-
尽管可以用this调用一个构造器,但却不能调用两个。
-
this() 和 super() 都指的是对象的引用,所以均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。
三、final
1、修饰变量:表示变量只能一次赋值以后值不能被修改(常量)
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
- 当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
- 如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值能变,即地址的值不发生变化。
- 当final修饰的是一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
public class Test3 {
public static final int a = 9; //初始化
public static final List<Integer> list = new LinkedList<>();
public static void main(String[] args) {
list.add(1);
list.add(2);
list.add(3);
// list = new ArrayList<>(); //报错 因为list被final修饰 不能更改
System.out.println(list); //正常打印 1 2 3
}
}
2、修饰类:表示该类不能被继承;
3、修饰方法:表示方法不能被重写;
final class Animal { //-------------final修饰的类
public String name;
public Animal( ) {
}
public void eat(String food) {
System.out.println("父类"+this.name + "正在吃" + food);
}
public void cc() {
System.out.println("父类cc");
}
}
class Cat extends Animal { //-------------无法继承
public Cat(String name) {
System.out.println("子类"+this.name);
}
@Override //--------无法继承导致无法重写
public void cc() {
System.out.println("子类cc");
}
}
4、final变量和普通变量的区别
- 当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。
- 由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c)); // true
System.out.println((a == e)); // false
}
}
四、static
1、修饰成员属性:将其变为类的成员,从而实现所有对象对于该成员的共享
虽然两个Person1都和Person2都分别设置了自己的年龄分别为1、99,但是由于被static修饰的为静态成员变量,它只有一份,内存在方法区上,是属于类的属性,不属于对象。所以最后打印的都是是一个最后修改的值99。
- 静态变量属于类,被所有对象共享,在内存中只有一个副本,在类初次加载的时候才会初始化,不依赖于对象的
- 非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响
- static成员变量初始化顺序按照定义的顺序来进行初始化
public class Person {
String name;
static int age;
public String toString() {
return "Name:" + name + ", Age:" + age;
}
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "Tom";
p1.age = 1;
Person p2 = new Person();
p2.name = "Cat";
p2.age = 99;
System.out.println(p1); // Name:Tom, Age:99
System.out.println(p2);// Name:Cat, Age:99
}
}
2、修饰成员方法:将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
static修饰成员方法最大的作用,就是可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗,比如main函数在调用其他非静态的方法就需要new对象
- 静态方法中不能访问非静态成员方法和非静态成员变量
- 非静态成员方法中是可以访问静态成员方法和静态成员变量
- 不依赖于对象,通过 类名.方法名 调用
- static方法是属于类的,非实例对象,也不能出现this、super;同时在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大,但是非static方法会在运行完毕后被虚拟机GC掉,减轻内存压力
public class Test3 {
private void function() {
System.out.println("非常静态的方法需要new对象");
}
public static void main(String[] args) {
Test3 test3 = new Test3();
test3.function();
}
}
3、静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
- 构造方法用于对象的初始化。静态初始化块,用于类的初始化操作。
- 在静态初始化块中不能直接访问非staic成员。
- 静态初始化块的作用就是:提升程序性能。
- 静态初始化块可以置于类中的任何地方,类中可以有多个静态初始化块。在类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次。
执行顺序:
①静态代码块
②实例代码块
③构造方法
class Book {
public Book(String msg) {
System.out.println(msg);
}
}
public class Person {
Book book1 = new Book("book1成员变量初始化");
static Book book2;
static {
book2 = new Book("static成员book2成员变量初始化");
book4 = new Book("static成员book4成员变量初始化");
}
public Person(String msg) {
System.out.println(msg);
}
Book book3 = new Book("book3成员变量初始化");
static Book book4;
public static void funStatic() {
System.out.println("static修饰的funStatic方法");
}
public static void main(String[] args) {
Person.funStatic();
System.out.println("****************");
Person p1 = new Person("p1初始化");
}
/**Output
* static成员book2成员变量初始化
* static成员book4成员变量初始化
* static修饰的funStatic方法
* ***************
* book1成员变量初始化
* book3成员变量初始化
* p1初始化
*/
}
五、抽象类和接口的异同点
不同点:
- 抽象类子类使用extends关键字来继承抽象类,接口实现类使用关键字implements来实现接口;
- 抽象类中除了抽象方法外,可以包含普通类的字段和方法;接口中方法只能是public abstract,字段只能是 public static final
- 抽象类可以有构造方法,接口中不能有构造方法。
- 抽象类中可以有普通成员变量,接口中没有普通成员变量
- 抽象类中可以包含静态方法,接口中不能包含静态方法
- 接口可以被多重实现,抽象类只能被单一继承
- 如果抽象类或者普通类实现了接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类或者普通类的子类中实现接口中方法
- 抽象类或者抽象方法 一定是不能被final,static,private 修饰的(重写不支持那三个)
相同点:
- 都不能被实例化
- 派生类必须重写未实现的方法
例子:
门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类或者接口来定义这个抽象概念:
abstract class Door {
public abstract void open();
public abstract void close();
}
或者---------------------------
interface Door {
public abstract void open();
public abstract void close();
}
但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:
①将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
②将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。
从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() {
//....
}
void close() {
//....
}
void alarm() {
//....
}
}