Java基础:面向对象编程6
1 Java this
和 super
关键字
1.1 this
关键字的作用
- 作为引用变量,指向当前对象:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之指向当前对象* @author: Yunyang* @date: 2024/10/15 10:24* @version:1.0**/
public class WithThisStudent {String name;int age;public WithThisStudent(String name, int age) {this.name = name;this.age = age;}public void out(){System.out.println(name+" " + age);}public static void main(String[] args) {WithThisStudent s1 = new WithThisStudent("zhangsan", 18);WithThisStudent s2 = new WithThisStudent("zhangsan", 16);s1.out();s2.out();}
}
运行结果:
zhangsan 18
zhangsan 16
分析:
- 在构造方法中,this.xxx 指向的就是实例变量,而不再是参数本身了。
- 如果参数名和实例变量名不同的话,就不必使用 this 关键字。
- 调用当前类的方法:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之调用当前类的方法* @author: Yunyang* @date: 2024/10/15 10:28* @version:1.0**/
public class InvokeCurrentClassMethod {void method1(){System.out.println("invoke method1");}void method2(){method1();}public static void main(String[] args) {new InvokeCurrentClassMethod().method2();}
}
运行结果
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之调用当前类的方法* @author: Yunyang* @date: 2024/10/15 10:28* @version:1.0**/
public class InvokeCurrentClassMethod {void method1(){System.out.println("invoke method1");}void method2(){method1();System.out.println("invoke method2");}public static void main(String[] args) {new InvokeCurrentClassMethod().method2();}
}
分析:
- 在一个类中使用 this 关键字来调用另外一个方法,如果没有使用的话,编译器会自动帮我们加上。
- 在源代码中,method2() 在调用 method1() 的时候并没有使用 this 关键字,但通过反编译后的字节码可以看得到。
this()
可以调用当前类的构造方法:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之调用当前类的构造方法* @author: Yunyang* @date: 2024/10/15 10:33* @version:1.0**/
public class InvokeConsructor {public InvokeConsructor() {System.out.println("hello");}public InvokeConsructor(int count){this();System.out.println(count);}public static void main(String[] args) {InvokeConsructor invokeConsructor = new InvokeConsructor(10);}
}
运行结果:
hello
10
分析:
- 在有参构造方法 InvokeConstrutor(int count) 中,使用了 this() 来调用无参构造方法 InvokeConstrutor()。
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之调用当前类的构造方法* @author: Yunyang* @date: 2024/10/15 10:38* @version:1.0**/
public class InvokeParamConstrutor {public InvokeParamConstrutor() {this(10); //this() 必须放在构造方法的第一行,否则就报错了System.out.println("hello");}public InvokeParamConstrutor(int count){System.out.println(count);}public static void main(String[] args) {InvokeParamConstrutor invokeParamConstrutor = new InvokeParamConstrutor();}
}
运行结果:
10
hello
分析:
- 也可以在无参构造方法中使用 this() 并传递参数来调用有参构造方法;
- 需要注意的是,this() 必须放在构造方法的第一行,否则就报错了。
this
可以作为参数在方法中传递:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之作为参数在方法中传递* @author: Yunyang* @date: 2024/10/15 10:40* @version:1.0**/
public class ThisAsParam {void method1(ThisAsParam p){System.out.println(p);}void method2(){method1(this);}public static void main(String[] args) {ThisAsParam thisAsParam = new ThisAsParam();System.out.println(thisAsParam);thisAsParam.method2();}
}
运行结果:
com.yunyang.javabetter.oop.thisandsuper.ThisAsParam@35851384
com.yunyang.javabetter.oop.thisandsuper.ThisAsParam@35851384
分析:
method2() 调用了 method1(),并传递了参数 this,method1() 中打印了当前对象的字符串。 main() 方法中打印了 thisAsParam 对象的字符串。从输出结果中可以看得出来,两者是同一个对象。
this
可以作为参数在构造方法中传递:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之作为参数在构造方法中传递* @author: Yunyang* @date: 2024/10/15 10:43* @version:1.0**/
public class ThisAsConstrutorParam {int count = 10;ThisAsConstrutorParam() {Data data = new Data(this);data.out();}public static void main(String[] args) {new ThisAsConstrutorParam();}
}class Data {ThisAsConstrutorParam param;Data(ThisAsConstrutorParam param) {this.param = param;}void out() {System.out.println(param.count);}
}
运行结果:
10
分析:
- 在构造方法 ThisAsConstrutorParam() 中,我们使用 this 关键字作为参数传递给了 Data 对象,它其实指向的就是 new ThisAsConstrutorParam() 这个对象。
- this 关键字也可以作为参数在构造方法中传递,它指向的是当前类的对象。当我们需要在多个类中使用一个对象的时候,这非常有用。
this
可以作为方法的返回值,返回当前类的对象:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: this用法之作为方法的返回值* @author: Yunyang* @date: 2024/10/15 10:44* @version:1.0**/
public class ThisAsMethodResult {ThisAsMethodResult getThisAsMethodResult() {return this;}void out() {System.out.println("hello");}public static void main(String[] args) {new ThisAsMethodResult().getThisAsMethodResult().out();}
}
运行结果:
hello
分析:
- getThisAsMethodResult() 方法返回了 this 关键字,指向的就是 new ThisAsMethodResult() 这个对象,所以可以紧接着调用 out() 方法——达到了链式调用的目的。
- 需要注意的是,this 关键字作为方法的返回值的时候,方法的返回类型为类的类型。
1.2 super
关键字的作用
- 指向父类对象:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: super用法之指向父类对象* @author: Yunyang* @date: 2024/10/15 11:29* @version:1.0**/
public class ReferParentField {public static void main(String[] args) {new Dog().printColor();}
}class Animal {String color = "白色";
}class Dog extends Animal {String color = "黑色";void printColor() {System.out.println(color);System.out.println(super.color);}
}
运行结果:
黑色
白色
分析:
- 如果父类和子类拥有同样名称的字段,super 关键字可以用来访问父类的同名字段。
- 父类 Animal 中有一个名为 color 的字段,子类 Dog 中也有一个名为 color 的字段,子类的 printColor() 方法中,通过 super 关键字可以访问父类的 color。
- 调用父类的方法:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: super用法之调用父类的方法* @author: Yunyang* @date: 2024/10/15 14:06* @version:1.0**/
public class ReferParentMethod {public static void main(String[] args) {new Dog().work();}
}class Animal {void eat() {System.out.println("吃...");}
}class Dog extends Animal {@Overridevoid eat() {System.out.println("吃...");}void bark() {System.out.println("汪汪汪...");}void work() {super.eat();bark();}
}
运行结果:
吃...
汪汪汪...
分析:
- 当子类和父类的方法名相同时,可以使用 super 关键字来调用父类的方法。换句话说,super 关键字可以用于方法重写时访问到父类的方法。
- 父类 Animal 和子类 Dog 中都有一个名为 eat() 的方法,通过 super.eat() 可以访问到父类的 eat() 方法。
super()
可以调用父类的构造方法:
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: super用法之调用父类的构造方法* @author: Yunyang* @date: 2024/10/15 14:13* @version:1.0**/
public class ReferParentConstructor {public static void main(String[] args) {new Dog();}
}class Animal {Animal(){System.out.println("动物来了");}
}class Dog extends Animal {Dog() {super();System.out.println("狗狗来了");}
}
运行结果:
动物来了
狗狗来了
分析:
- 子类 Dog 的构造方法中,第一行代码为 super(),它就是用来调用父类的构造方法的。
- 在默认情况下,super() 是可以省略的,编译器会主动去调用父类的构造方法。也就是说,子类即使不使用 super() 主动调用父类的构造方法,父类的构造方法仍然会先执行。
/*** @package: com.yunyang.javabetter.oop.thisandsuper* @description: super用法之调用父类的有参构造函数* @author: Yunyang* @date: 2024/10/15 14:16* @version:1.0**/
class Person {int id;String name;Person(int id, String name) {this.id = id;this.name = name;}
}class Emp extends Person {float salary;Emp(int id, String name, float salary) {super(id, name);this.salary = salary;}void display() {System.out.println(id + " " + name + " " + salary);}
}public class CallParentParamConstrutor {public static void main(String[] args) {new Emp(1, "Zhangsan", 20000f).display();}
}
运行结果:
1 Zhangsan 20000.0
分析:
- super() 也可以用来调用父类的有参构造方法,这样可以提高代码的可重用性;
- Emp 类继承了 Person 类,也就继承了 id 和 name 字段,当在 Emp 中新增了 salary 字段后,构造方法中就可以使用 super(id, name) 来调用父类的有参构造方法。
1.3 小结
this
关键字:用于引用当前对象,调用当前类的方法,调用当前类的构造方法,作为参数传递,以及作为方法的返回值。super
关键字:用于引用父类对象,调用父类的方法,以及调用父类的构造方法。
2 Java static
关键字
2.1 概述
static
关键字的作用可以用一句话来描述:“方便在没有创建对象的情况下进行调用,包括变量和方法”。也就是说,只要类被加载了,就可以通过类名进行访问。static
可以用来修饰类的成员变量和成员方法。
2.2 静态变量
- 定义:如果在声明变量的时候使用了
static
关键字,那么这个变量就被称为静态变量。 - 特点:
- 静态变量只在类加载的时候获取一次内存空间,这使得静态变量很节省内存空间。
- 由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留。
- 静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问,否则编译器会发出警告。
2.2 静态方法
- 定义:如果方法上加了
static
关键字,那么它就是一个静态方法。 - 特征:
- 静态方法属于这个类而不是这个类的对象。
- 调用静态方法的时候不需要创建这个类的对象。
- 静态方法可以访问静态变量。
- 限制:
- 静态方法不能访问非静态变量和调用非静态方法。
- 为什么
main
方法是静态的:- 如果
main
方法不是静态的,就意味着 Java 虚拟机在执行的时候需要先创建一个对象才能调用main
方法,而main
方法作为程序的入口,创建一个额外的对象显得非常多余。
- 如果
- 示例:
java.lang.Math
类的几乎所有方法都是静态的,可以直接通过类名来调用,不需要创建类的对象。
2.3 静态代码块
- 定义:用一个
static
关键字,外加一个大括号括起来的代码被称为静态代码块。 - 作用:
- 静态代码块通常用来初始化一些静态变量,它会优先于
main()
方法执行。 - 静态代码块在初始化集合的时候非常有用。在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。
- 静态代码块通常用来初始化一些静态变量,它会优先于
2.4 静态内部类
- 定义:Java 允许我们在一个类中声明一个内部类,它提供了一种令人信服的方式,允许我们只在一个地方使用一些变量,使代码更具有条理性和可读性。
- 常见的内部类:
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
- 静态内部类实现单例:
- 第一次加载
Singleton
类时并不会初始化instance
,只有第一次调用getInstance()
方法时 Java 虚拟机才开始加载SingletonHolder
并初始化instance
,这样不仅能确保线程安全,也能保证Singleton
类的唯一性。
- 第一次加载
- 注意:
- 静态内部类不能访问外部类的所有成员变量。
- 静态内部类可以访问外部类的所有静态变量,包括私有静态变量。
- 外部类不能声明为
static
。
2.5 示例代码
2.5.1 静态变量
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态变量2* @author: Yunyang* @date: 2024/10/15 14:30* @version:1.0**/
public class Counter {static int count = 0;public Counter() {count++;System.out.println(count);}public static void main(String[] args) {Counter c1 = new Counter();Counter c2 = new Counter();Counter c3 = new Counter();}
}
运行结果:
1
2
3
分析:
- 由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3。
2.5.2 静态方法
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态方法* @author: Yunyang* @date: 2024/10/15 14:37* @version:1.0**/
public class StaticMethodStudent {String name;int age;static String school = "郑州大学";public StaticMethodStudent(String name, int age) {this.name = name;this.age = age;}static void change() {school = "河南大学";}void out() {System.out.println(name + " " + age + " " + school);}public static void main(String[] args) {StaticMethodStudent.change();StaticMethodStudent s1 = new StaticMethodStudent("沉默王二", 18);StaticMethodStudent s2 = new StaticMethodStudent("沉默王三", 16);s1.out();s2.out();}
}
运行结果:
沉默王二 18 河南大学
沉默王三 16 河南大学
分析:
- change() 方法就是一个静态方法,所以它可以直接访问静态变量 school,把它的值更改为河南大学;并且,可以通过类名直接调用 change() 方法,就像 StaticMethodStudent.change() 这样。
2.5.3 静态代码块
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态代码块* @author: Yunyang* @date: 2024/10/15 14:40* @version:1.0**/
public class StaticBlock {static {System.out.println("静态代码块");}public static void main(String[] args) {System.out.println("main 方法");}
}
运行结果:
静态代码块
main 方法
分析:
- 静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行。
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态代码块* @author: Yunyang* @date: 2024/10/15 14:41* @version:1.0**/
public class StaticBlockNoMain {static {System.out.println("静态代码块,没有 main");}
}
运行结果:
D:\Gitee\JavaKnowledgeBase>java StaticBlockNoMain
错误: 找不到或无法加载主类 StaticBlockNoMain
分析:
- 在命令行中执行 java StaticBlockNoMain 的时候,会抛出 NoClassDefFoundError 的错误;
- Java 7 开始没有 main() 方法的 Java 类无法执行。
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态代码块* @author: Yunyang* @date: 2024/10/15 14:42* @version:1.0**/
public class StaticBlockDemo {public static List<String> writes = new ArrayList<>();static {writes.add("沉默王二");writes.add("沉默王三");writes.add("沉默王四");System.out.println("第一块");}static {writes.add("沉默王五");writes.add("沉默王六");System.out.println("第二块");}public static void main(String[] args) {}
}
运行结果:
第一块
第二块
分析:
- writes 是一个静态的 ArrayList,所以不太可能在声明的时候完成初始化,因此需要在静态代码块中完成初始化
2.5.4 静态内部类实现单例
/*** @package: com.yunyang.javabetter.oop.staticdemo* @description: 静态内部类* @author: Yunyang* @date: 2024/10/15 14:44* @version:1.0**/
public class Singleton {private Singleton(){}private static class SingletonHolder{public static final Singleton insstance = new Singleton();}public static Singleton getInstance(){return SingletonHolder.insstance;}
}
分析:
- 第一次加载 Singleton 类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
2.6 小结
static
关键字在 Java 中用于修饰类的成员变量和方法,使得它们可以在没有创建对象的情况下通过类名直接访问。静态变量和静态方法在类加载时初始化,静态代码块用于初始化静态变量,静态内部类可以用于实现单例模式。合理使用 static
关键字可以提高代码的可读性和性能。
3 Java final
关键字
3.1 final
变量
- 定义:被
final
修饰的变量无法重新赋值。换句话说,final
变量一旦初始化,就无法更改。 - 特点:
final
修饰的成员变量必须有一个默认值,否则编译器将会提醒没有初始化。final
和static
一起修饰的成员变量叫做常量,常量名必须全部大写。- 用
final
关键字来修饰参数,它意味着参数在方法体内不能被再修改。如果尝试去修改它的话,编译器会提示错误。
3.2 final
方法
- 定义:被
final
修饰的方法不能被重写。如果我们在设计一个类的时候,认为某些方法不应该被重写,就应该把它设计成final
的。 - 示例:
Thread
类就是一个例子,它本身不是final
的,这意味着我们可以扩展它,但它的isAlive()
方法是final
的。该方法是一个本地(native)方法,用于确认线程是否处于活跃状态。而本地方法是由操作系统决定的,因此重写该方法并不容易实现。
- 区别:
- 一个类是
final
的,和一个类不是final
,但它所有的方法都是final
的,它们之间有什么区别?- 前者不能被继承,也就是说方法无法被重写;后者呢,可以被继承,然后追加一些非
final
的方法。
- 前者不能被继承,也就是说方法无法被重写;后者呢,可以被继承,然后追加一些非
- 一个类是
3.3 final
类
- 定义:如果一个类使用了
final
关键字修饰,那么它就无法被继承。 - 示例:
String
类就是一个final
类。
- 原因:
- 为了实现字符串常量池:
String
类被设计成final
,可以确保字符串常量池中的字符串对象不可变,从而提高性能和安全性。 - 为了线程安全:
String
对象的不可变性使得它在多线程环境下是线程安全的。 - 为了 HashCode 的不可变性:
String
对象的不可变性确保了hashCode
方法的返回值在对象创建后不会改变,这对于使用String
作为HashMap
的键非常重要。
- 为了实现字符串常量池:
- 注意:
- 任何尝试从
final
类继承的行为将会引发编译错误。 - 类是
final
的,并不意味着该类的对象是不可变的。 - 把一个类设计成
final
的,有其安全方面的考虑,但不应该故意为之,因为把一个类定义成final
的,意味着它没办法继承,假如这个类的一些方法存在一些问题的话,我们就无法通过重写的方式去修复它。
- 任何尝试从
3.4 示例代码
3.4.1 final
变量
public class FinalVariableExample {final int finalVar = 10;static final int CONSTANT_VAR = 20;public void method(final int param) {// param = 30; // 编译错误,final 参数不能被修改System.out.println("Final variable: " + finalVar);System.out.println("Constant variable: " + CONSTANT_VAR);}public static void main(String[] args) {FinalVariableExample example = new FinalVariableExample();example.method(40);}
}
3.4.2 final
方法
public class FinalMethodExample {public final void finalMethod() {System.out.println("This is a final method");}public static void main(String[] args) {FinalMethodExample example = new FinalMethodExample();example.finalMethod();}
}class SubClass extends FinalMethodExample {// 编译错误,无法重写 final 方法// public void finalMethod() {// System.out.println("Overriding final method");// }
}
3.4.3 final
类
public final class FinalClassExample {public void method() {System.out.println("This is a method in a final class");}public static void main(String[] args) {FinalClassExample example = new FinalClassExample();example.method();}
}// 编译错误,无法继承 final 类
// class SubClass extends FinalClassExample {
// }
3.5 小结
final
关键字在 Java 中用于修饰变量、方法和类,具有以下特点:
final
变量:一旦初始化后无法更改。final
方法:不能被重写。final
类:不能被继承。
4 Java instanceof
关键字
4.1 作用
instanceof
关键字用于判断一个对象是否符合指定的类型,结果要么是 true
,要么是 false
。
4.2 用法
(object) instanceof (type)
4.3 示例
在反序列化的时候,instanceof
操作符还是蛮常用的,因为这时候我们不太确定对象是否属于指定的类型,如果不进行判断的话,就容易抛出 ClassCastException
异常。
public class InstanceOfExample {public static void main(String[] args) {Object obj = "Hello, World!";if (obj instanceof String) {System.out.println("obj is an instance of String");} else {System.out.println("obj is not an instance of String");}}
}
4.4 继承与 instanceof
Java 是一门面向对象的编程语言,也就意味着除了基本数据类型,所有的类都会隐式继承 Object
类。因此,任何对象都可以被视为 Object
的实例。
public class InstanceOfExample2 {public static void main(String[] args) {Object obj = new ArrayList<>();if (obj instanceof List) {System.out.println("obj is an instance of List");}if (obj instanceof ArrayList) {System.out.println("obj is an instance of ArrayList");}if (obj instanceof Object) {System.out.println("obj is an instance of Object");}}
}
4.5 null
与 instanceof
对于 null
来说,instanceof
的结果为 false
。因为所有的对象都可以为 null
,所以也不好确定 null
到底属于哪一个类。
public class InstanceOfExample3 {public static void main(String[] args) {Object obj = null;if (obj instanceof String) {System.out.println("obj is an instance of String");} else {System.out.println("obj is not an instance of String");}}
}
4.6 小结
instanceof
关键字在 Java 中用于判断一个对象是否符合指定的类型,结果要么是 true
,要么是 false
。它在类型检查和避免 ClassCastException
异常时非常有用。需要注意的是,对于 null
对象,instanceof
的结果总是 false
。
5 思维导图
6 参考链接
- 详解Java this与super关键字的用法与区别
- 详解 Java static 关键字的作用:静态变量、静态方法、静态代码块、静态内部类
- 一文彻底搞懂 Java final 关键字
- 掌握 Java instanceof关键字