在Java中,面向对象编程更是核心设计理念之一,为开发者提供了丰富的工具和特性来创建灵活、可扩展的应用程序。
本博客将深入探讨Java面向对象编程的高级特性,包括但不限于多态、继承、封装、抽象类、接口等方面的内容。我们将从实际案例出发,结合代码示例和最佳实践,带您逐步掌握这些高级特性的精髓,并指导您如何在实际项目中应用它们。
目录
1、关键词:static
1.1 类属性、类方法的设计思想
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份
。
此外,在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用者和当前类的对象无关,这样的方法通常被声明为类方法
,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
这里的类变量、类方法,只需要使用static
修饰即可。所以也称为静态变量、静态方法。
1.2 static关键字
使用范围:在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
1、随着类的加载而加载
2、优先于对象存在
3、修饰的成员,被所有对象所共享
4、访问权限允许时,可不创建对象,直接被类调用
1.3 静态变量
1.3.1 语法格式
使用static修饰的成员变量就是静态变量(或类变量、类属性)
[修饰符] class 类{
[其他修饰符] static 数据类型 变量名;
}
1.3.2 静态变量的特点
-
静态变量的默认值规则和实例变量一样。
-
静态变量值是所有对象共享。
-
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
-
如果权限修饰符允许,在其他类中可以通过“
类名.静态变量
”直接访问,也可以通过“对象.静态变量
”的方式访问(但是更推荐使用类名.静态变量的方式)。 -
静态变量的get/set方法也静态的,当局部变量与静态变量
重名时
,使用“类名.静态变量
”进行区分。
示例:
class Chinese{
//实例变量
String name;
int age;
//类变量
static String nation;//国籍
public Chinese() {
}
public Chinese(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
", nation='" + nation + '\'' +
'}';
}
}
public class StaticTest {
public static void main(String[] args) {
Chinese c1 = new Chinese("张三",36);
c1.nation = "中华人民共和国";
Chinese c2 = new Chinese("老干妈",66);
System.out.println(c1);
System.out.println(c2);
System.out.println(Chinese.nation);
}
}
1.4 静态方法
1.4.1 语法格式
用static修饰的成员方法就是静态方法。
[修饰符] class 类{
[其他修饰符] static 返回值类型 方法名(形参列表){
方法体
}
}
1.4.2 静态方法的特点
-
静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
-
只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
-
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
-
静态方法可以被子类继承,但不能被子类重写。
-
静态方法的调用都只看编译时类型。
-
因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
示例:
public class Father {
public static void method(){
System.out.println("Father.method");
}
public static void fun(){
System.out.println("Father.fun");
}
}
public class Son extends Father{
// @Override //尝试重写静态方法,加上@Override编译报错,去掉Override不报错,但是也不是重写
public static void fun(){
System.out.println("Son.fun");
}
}
public class TestStaticMethod {
public static void main(String[] args) {
Father.method();
Son.method();//继承静态方法
Father f = new Son();
f.method();//执行Father类中的method
}
}
2、 单例(Singleton )设计模式
2.1 设计模式概述
设计模式是在大量的实践中总结
和理论化
之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。
经典的设计模式共有23种。每个设计模式均是特定环境下特定问题的处理方法。
2.2 何为单例设计模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
2.3 实现思路
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
2.4 单例模式的两种实现方式
2.4.1 饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
2.4.2 懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
2.4.3 饿汉式 vs 懒汉式
饿汉式:
-
特点:
立即加载
,即在使用类的时候已经将对象创建完毕。 -
优点:实现起来
简单
;没有多线程安全问题。 -
缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会
耗费内存
。
懒汉式:
-
特点:
延迟加载
,即在调用静态方法时实例才被创建。 -
优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会
节约内存
。 -
缺点:在多线程环境中,这种实现方法是完全错误的,
线程不安全
,根本不能保证单例的唯一性。
3、理解main方法的语法
由于JVM需要调用类的main()方法,所以该方法的访问权限必须是public,又因为JVM在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
命令行参数用法示例
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
4、代码块
代码块(或初始化块)的作用:对Java类或对象进行初始化
代码块(或初始化块)的分类:
1、一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block)
2、没有使用static修饰的,为非静态代码块。
4.1 静态代码块
如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。
4.1.1 语法格式
在代码块的前面加static,就是静态代码块。
[修饰符] class 类{
static{
静态代码块
}
}
4.1.2 静态代码块的特点
1、可以有输出语句。
2、可以对类的属性、类的声明进行初始化操作。
3、不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4、若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5、静态代码块的执行要先于非静态代码块。
6、静态代码块随着类的加载而加载,且只执行一次。
示例:
public class Chinese {
// private static String country = "中国";
private static String country;
private String name;
{
System.out.println("非静态代码块,country = " + country);
}
static {
country = "中国";
System.out.println("静态代码块");
}
public Chinese(String name) {
this.name = name;
}
}
public class TestStaticBlock {
public static void main(String[] args) {
Chinese c1 = new Chinese("张三");
Chinese c2 = new Chinese("李四");
}
}
4.2 非静态代码块
4.2.1 语法格式
[修饰符] class 类{
{
非静态代码块
}
[修饰符] 构造器名(){
// 实例初始化代码
}
[修饰符] 构造器名(参数列表){
// 实例初始化代码
}
}
4.2.2 非静态代码块的作用
和构造器一样,也是用于实例变量的初始化等操作。
4.2.3 非静态代码块的意义
如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
4.2.4 非静态代码块的执行特点
1、可以有输出语句。
2、可以对类的属性、类的声明进行初始化操作。
3、除了调用非静态的结构外,还可以调用静态的变量或方法。
4、若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
5、每次创建对象的时候,都会执行一次。且先于构造器执行。
4.3 代码示例
声明User类
1、包含属性:username(String类型),password(String类型),registrationTime(long类型),私有化
2、包含get/set方法,其中registrationTime没有set方法
3、包含无参构造
输出“新用户注册”,
registrationTime赋值为当前系统时间,
username就默认为当前系统时间值,
password默认为“123456”
4、包含有参构造(String username, String password),
输出“新用户注册”,
registrationTime赋值为当前系统时间,
username和password由参数赋值
5、包含public String getInfo()方法,返回:“用户名:xx,密码:xx,注册时间:xx”
编写测试类,测试类main方法的代码如下:
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.getInfo());
User u2 = new User("song","8888");
System.out.println(u2.getInfo());
}
如果不用非静态代码块,User类是这样的:
public class User {
private String username;
private String password;
private long registrationTime;
public User() {
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
username = registrationTime+"";
password = "123456";
}
public User(String username,String password) {
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
public String getInfo(){
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
如果提取构造器公共代码到非静态代码块,User类是这样的:
public class User {
private String username;
private String password;
private long registrationTime;
{
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
}
public User() {
username = registrationTime+"";
password = "123456";
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
public String getInfo(){
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
5、final关键字
5.1 final的意义
final:最终的,不可更改的
5.2 final的使用
5.2.1 final修饰类
表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。
例如:String类、System类、StringBuffer类
final class Eunuch{
}
class Son extends Eunuch{//错误,Eunuch不能被继承
}
5.2.2 final修饰方法
表示这个方法不能被子类重写。
例如:Object类中的getClass()
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
5.2.3 final修饰变量
final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。
例如:final double MY_PI = 3.14;
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
修饰成员变量:
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
}
}
修饰局部变量:
public class TestFinal {
public static void main(String[] args){
final int MIN_SCORE ;
MIN_SCORE = 0;
final int MAX_SCORE = 100;
MAX_SCORE = 200; //非法
}
}