1. this/super关键字
this:Java中的关键字,代表调用当前类中实例变量与实例方法的那个对象(this即对象本身)。this中存储的是对象的引用(堆内存中的地址),可以将this理解为指向对象本身的指针。
this的用法(3种):
1)直接引用(多用于以对象作为参数传递):
可以直接通过this.xxx直接调用对象的实例方法与实例变量。
this代表对象本身,当考虑到需要传递当前对象时,可以使用this。
/**
* @description Eat Apple
* @author Rock
* @date 2021/07/01
*/
public class AppleClear{
// Clear Apple
public static Apple clearApple(Apple apple){
System.out.println("已清洗苹果");
}
// main(): 完成吃苹果的动作
public static void main(String[] args){
Apple newApple = new Apple();
newApple.eatApple();
}
// Apple class
class Apple{
// 无参构造
public Apple(){}
// eat Apple
public void eatApple(){
// 清洗苹果
clearApple(this);
// 吃苹果
System.out.println("已吃完苹果");
}
}
}
以上实例中,创建Apple类的对象newApple后,在实例方法eatApple()中通过this关键字将当前调用该方法的对象(newApple)传递给了静态方法clearApple()。
2)形参名与成员变量名重复时通过this区分(构造方法/实例方法):
当调用方法的形参名与实例变量的名字重复时,通过this.实例变量名标注以区分。默认情况下,this是可以省略的。
public class BankAccount{
// 实例变量
int account_id;
String userName;
// 构造方法
public BankAccount(int account_id, String userName){
this.account_id = account_id;
this.userName = userName;
}
// set()
public void setAccount_id(int account_id){
this.account_id = account_id;
}
// set()
public void setUserName(String userName){
this.userName = userName;
}
}
以上实例中,在构造方法与实例方法set中,通过this关键字标识了实例变量,用以区分形参(否则编译器无法识别)。
3)在构造方法内部通过this(参数列表)调用同类中形参列表匹配的构造方法:
这个和super()调用超类构造方法一起讲;
super:Java中的关键字,可以理解为指向当前对象的超类(最近的一个超类)的指针,注意,this存储的是实际的地址(堆内存地址),super只是一个关键字,不存储任何地址。
super最常见的时候是使用在继承时,当子类继承父类时会自动继承父类的所有成员变量与方法,成员变量(静态+实例)可以直接通过super.xxx/子类对象.xxx调用。在通过子类对象调用父类中未重写的方法时,默认使用super.methodName()。注意:在子类继承的父类构造方法中,默认调用super(形参列表匹配)父类中的构造方法。
super的用法(3种):
1)直接引用:
可以通过super.xxx直接引用当前对象最近超类(直接继承类)的类的实例成员(变量/方法)。
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// 调用重写的run()方法
dog.run();
// 调用父类的run()方法
dog.run_super();
}
}
class Animal {
// 实例变量age
int age;
// 实例方法run
public void run(){
System.out.println("Animal is running");
}
}
class Dog extends Animal{
public void run_super(){
super.run();
}
@Override
public void run() {
System.out.println("Dog is running");
}
}
2)用于区分超类的方法与子类重写的同名方法:
例如:当子类没有重写父类中的run()方法时,子类对象.run()默认调用super.run(),即父类中的run()方法。如果子类重写了run()方法,子类对象.run()默认调用this.run()。例如下例中在run_all()
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// 调用run_all()方法
dog.run_all();
}
}
class Animal {
// 实例变量age
int age;
// 实例方法run
public void run(){
System.out.println("Animal is running");
}
}
class Dog extends Animal{
public void run_all(){
// 调用超类run()
super.run();
// 调用本类run()
run();
}
@Override
public void run() {
System.out.println("Dog is running");
}
}
3)引用构造方法(超类)
super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
class Animal {
// 实例变量age
int age;
// 构造器
public Animal() {}
public Animal(int age) {
this.age = age;
}
}
class Dog extends Animal{
String animalName;
// 构造器
public Dog() {
this(0,null);
}
public Dog(int age) {
this(age,null);
}
public Dog(int age, String animalName) {
super(age);
this.animalName = animalName;
}
}
注意:super()与this()必须出现在构造器的第一行,并且两者只能出现一个。
注意:在子类的构造器中调用父类的构造器并不会创建父类对象,对象是通过new关键字开辟堆内存创建的,构造器的主要作用是初始化实例变量。之所以在子类中调用父类的构造器是为了初始化继承自父类的实例变量。
this与super的异同:
1)super(args):调用最近超类中的同参构造器(应为构造器的第一条语句,否则编译不通过);
2)this(args):调用本类中的同参构造器(应为构造器的第一条语句,否则编译不通过);
3)super:引用当前对象的直接父类中的成员(当子类与超类类中有相同成员定义时通过super指明该成员是超类成员还是当前类成员。如:super.变量名 super.成员函数据名(实参)) ;this:它代表当前对象,是当前对象的引用(在本类中易产生二义性之处,应使用 this 来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用 this 来指明成员变量名)
4)在子类的构造器中,如果无参数,构造器第一行会隐式的通过调用 super()子句调用父类的无参构造器;如果父类里面没有定义参数为空的构造函数,那么必须在子类的构造函数的第一行显示的调用super(参数)以调用父类当中其它的构造函数;如果子类当中的构造函数的第一行通过this(参数)调用了本类的构造函数,那么就隐式的那个super()语句就会消失。
5)this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。
6)从本质上讲,this 是一个指向本对象的指针(存储对象在堆内存中的地址), 然而 super 是一个 Java 关键字(不存储地址)。
2. 可见性修饰符(访问权限):
Java中为类、变量、方法的访问提供了4种访问修饰符:依照权限从大到小依次是:public、protected、default、private。其访问权限设置如下
· pbulic:访问权限最广的修饰符,其修饰的类、变量、方法在内外包均可以访问;
· protected:对于protected修饰的成员,对于本包和子类可见(注意:继承中的protected修饰符);
· default:默认修饰符(未添加可见修饰符时默认设置),对于同包的类具有访问权限;
· private:私有的权限,private修饰的成员(变量/方法)能被本类的成员访问;
**注意:继承与访问控制符无关,子类会自动继承父类的所有属性与方法,访问控制符只是控制该变量/方法/类的访问权限,与继承无关。但父类中private/default修饰的变量/方法不允许不允许子类访问、调用或重写,其只允许父类的实例访问,或使用父类提供的接口访问,所以abstract类中的抽象方法不能用private修饰(无法重写)。
访问修饰符的使用实例:
1)外部类/外部接口:(public/default):
外部类/外部接口是相对于内部类/内部接口而言的,主要是为了保证所有的类都可以使用他们。
// 访问权限为public的外部类
public class OuterClass {
}
// 访问权限为public的外部接口
public interface OuterInterface {
}
2)类成员:
类成员可以使用所有的四种访问权限。
*注意:
这里描述的类成员为成员变量与成员方法(均包括实例的与静态的),局部成员(局部变量与局部内部类)不需要任何修饰符,添加修饰符会导致编译无法通过。
*数据域封装(重点):
数据域封装的含义是,对数据域(实例的属性:实例变量)使用 private 修饰符,将数据域声明为私有域。如果不使用数据域封装,则数据域的值可以从类的外部直接修改,导致数据被篡改以及类难以维护。使用数据域封装的目的是为了避免直接修改数据域的值。
同时,在定义私有数据域的类之外,不能通过直接引用的方式访问该私有数据域,但是仍然可能需要读取和修改数据域的值。为了能够读取私有数据域的值,可以编写 get 方法(称为读取器或访问器)返回数据域的值。为了能够修改私有数据域的值,可以编写 set 方法(称为设置器或修改器)将数据域的值设置为新值,例如:
// 银行账户类
public class Account {
// 私有的账户id与用户名
private int accountId;
private String name;
// 设置对外访问/修改的API
// 获取账户id
public int getAccountId() {
return accountId;
}
// 修改账户id
public void setAccountId(int accountId) {
this.accountId = accountId;
}
// 获取用户名
public String getName() {
return name;
}
// 修改用户名
public void setName(String name) {
this.name = name;
}
}
3)抽象类(抽象方法3种):
抽象类之所以为抽象类,是因为它是作为父类来使用的,是等待子类去实现的,而父类中 private的权限只能是由父类对象访问自个,子类是不能访问或重写的,所以在抽象类中方法为abstract时只有public,protected,default三种访问权限。(只要有一个抽象方法,该类即为抽象类;抽象类中并不是所有方法都是抽象方法)。
4)接口成员(只有三种)的访问权限
接口由于其的特殊性,所有成员的访问权限都规定得死死的(不可修改,可以少写修饰符,但不能写错修饰符),下面是接口成员的访问权限:
· 常量:public static final;可以被接口/类继承
· 抽象方法:public abstrct;可以被继承,但必须重写;
· 静态方法:public static(JDK1.8以后支持)
· 内部类/接口:public static;
interface Test{
// 常量:省略了public static final
int i = 0;
// 抽象方法:省略了public abstract
void print();
// 静态方法:省略了public
static void printing(){ }
// 内部类:省略了public static
class InnerTest{ }
// 内部接口:省略了public static
interface InnerTest_2{ }
}
5)构造器:
构造器可以使用4种访问权限,但子类的构造器默认调用父类的构造器初始化父类继承过来的对象,一旦调用失败,意味着子类继承失败(单例模式:private 构造器)
其他修饰符:
· static:声明其修饰的变量、方法、内部类/接口(不能修饰常规类/接口)是静态的,静态变量在类加载时创建并初始化,所有的静态/实例方法均可以调用;静态方法可以被静态/实例方法调用。注意:static修饰的代码块为静态代码块,在类加载时执行。
· final:其声明的对象不可修改;final修饰的变量必须在声明时给定初值(空白final除外),其中通过public static final声明一个全局共享的基本类型常量,并存入常量池中;final修饰的方法不可重写,即不可被子类覆盖;final修饰的类不可以被继承。
· abstract:其声明的类、方法是抽象的;abstract声明的类为抽象类,抽象类不能创建实例;abstract声明的方法为抽象方法,抽象方法没有方法体,子类继承父类/实现接口的抽象方法时需要重写该方法。
3. 字符串
字符串是常用的数据类型。Java中常见的字符串类型包括String、StringBuffer、StringBuilder。
1) String:
虽然Java中的String是一个引用类型,但是从String的源码中可以看到String通过数组存储,并且该数组被关键字final修饰,说明String类型的对象一旦创建就要赋初始值,并且是不可修改的。事实上,String对象被存储于JVM的字符串常量池中,而非堆内存中,这意味着每次对String的赋值、修改操作都会在字符串常量池中创建一个新的对象。
2)StringBuffer & StringBuilder:
StringBuffer 与 StringBuilder 都是 AbstractStringBuilder 的子类,都采用数组存储字符串,但由于没有用关键字final修饰,因此数组内容可变。StringBuffer 和 StringBuilder 都是可变类型,可以对字符串的内容进行修改,且不会因为修改而创建新的对象。
在需要经常对字符串的内容进行修改的情况下,应使用 StringBuffer 或 StringBuilder,在时间和空间方面都显著优于 String。
*注意: StringBuffer 与 StringBuilder的区别
从源码可以看到,StringBuffer 对定义的方法或者调用的方法使用了关键词 synchronized 修饰,而 StringBuilder 的方法没有使用关键词 synchronized 修饰。由于 StringBuffer 对方法加了同步锁,因此其效率略低于 StringBuilder,但是在多线程的环境下,StringBuilder 不能保证线程安全,因此 StringBuffer 是更优的选择。