实现封装。创建和使用单例类和不可变类。开发在初始化块、变量、方法和类上使用 static 关键字的代码。开发使用最终关键字的代码。
封装
最好尽可能多地隐藏类的内部实现。减轻变化的影响。
class MyClass {
String myField = "you";
void myMethod() {
System.out.println("Hi " + myField);
}
}
如果 myField 将其类型从 String 更改为 int 会怎样?
如果我们在五十个类中使用这个属性,我们将不得不在程序中做五十个更改。
但是如果我们隐藏属性并使用 setter 方法,我们可以避免五十个变化:
// Hiding the attr to the outside world with the private keyword
private int myField = 0;
void setMyField(String val) { // Still accepting a String
try {
myField = Integer.parseInt(val);
} catch(NumberFormatException e) {
myField = 0;
}
}
如果某个东西被定义为 protected,那么它只能被定义它的类、它的子类和同一个包的类访问。子类是否在另一个包中并不重要,这使得这个修饰符的限制比 private 少。
如果没有修饰符,则它具有默认访问权限,也称为包访问权限,因为它只能由同一个包中的类访问。如果在另一个包中定义了一个子类,则它不能看到默认的访问属性或方法。这是 protected 修饰符的唯一区别,这使得它更具限制性。
package street21;
public class House {
protected int number;
private String reference;
void printNumber() {
System.out.println("Num: " + number);
}
public void printInformation() {
System.out.println("Num: " + number);
System.out.println("Ref: " + reference);
}
}
class BlueHouse extends House {
public String getColor() { return "BLUE"; }
}
...
package city;
import street21.*;
class GenericHouse extends House {
public static void main(String args[]) {
GenericHouse h = new GenericHouse();
h.number = 100;
h.reference = ""; // Compile-time error reference为private
h.printNumber(); // Compile-time error printNumber为默认default,只有同一个包下才可以访问,子包下也不行
h.printInformation();
BlueHouse bh = new BlueHouse(); // Compile-time error BlueHouse为default,默认访问权限
bh.getColor(); // Compile-time error
}
}
单例
某个特定类只有一个实例。这样的类是一个单例类,是一个设计模式。
有一些类是用 Java 单例模式编写的,例如:
-
java.lang.Runtime
-
java.awt.Desktop
Java 中,一般来说,有两种方法来实现单例类:
- 构造函数私有化,提供静态工厂返回实例
- 作为枚举类型
第一种类型
有两种方式实现
使用一个私有静态属性和一个获取它的方法
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
- 属性必须是私有的,这样其他类只能通过它的 getter 来使用它。
- 它必须是静态的,以便在任何人都可以使用它之前,在加载类时可以创建实例,因为静态成员属于类,而不属于特定的实例。
- 它必须是最终的,这样以后就不能创建新的实例。
变种,使用静态内部类
class Singleton {
private Singleton() { }
private static class Holder {
private static final Singleton instance =
new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
这样做的好处是,在第一次引用内部类之前,不会创建实例。
get 方法中创建实例
有时候创建对象并不像调用 new 那么简单
class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
// more code to create the instance...
}
return instance;
}
}
第一次调用此方法时,将创建实例。
但是用这种方法,我们面临另一个问题。在多线程环境中,如果两个或多个线程并行执行此方法,则很有可能最终出现类的多个实例。
一个解决方案是同步这个方法,这样一次只有一个线程可以访问它。
class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这样做的问题是,严格来说,效率并不高。我们只需要在第一次创建实例时进行同步,而不需要在每次调用方法时进行同步。
一个改进是只锁定创建实例的代码的一部分。为了正确地工作,我们必须再次检查实例是否为 null,一个没有锁定(检查实例是否已经创建) ,然后在同步块中再创建一个实例(以安全地创建实例)。
class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这种实现还不是完美的。这一次,问题出在 Java 虚拟机(JVM)级别。JVM (有时是编译器)可以通过重新排序或缓存变量的值(并且不使更新可见)来优化代码。
解决方案是使用 volatile 关键字,它保证许多线程共享的变量的任何读/写操作都是原子操作,而不是缓存操作。
class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
不可变对象
在多个类使用时,还可能不希望修改对象的值或状态。这样的对象将会是一个不可变对象。
在 Java JDK 中有一些不可变的类,例如:
java.lang.String
不可变对象在创建后不能更改。这意味着它们不能有 setter 方法或公共变量,因此设置其属性的唯一方法是通过构造函数。
不可变性还意味着,如果一个方法必须更改对象的属性,因为对象或其值不能更改,那么它必须返回对象的副本和新的值(这正是 String 类的工作方式)。
- 我个人认为使用lombok插件在构造set和get方法时,因为get没有返回属性的副本,其实严格来说并没有把对象封装好,换言之,只需要得到返回的内存地址就可以直接修改对象属性,并不需要使用set方法
另一个需要考虑的问题是继承。如果不可变类可以被继承,那么子类可以改变方法来修改类的实例,所以不可变类不允许这样做。
不可变类需要满足的条件
- 通过构造函数设置它的所有属性
- 不定义 setter 方法
- 声明它的所有属性private(有时候还要加上final)
- 声明类final,防止继承
- 保护对任何可变状态的访问成员,例如又一个成员属性对象为List,则引用不能在对象外部访问,或者必须返回副本(如果对象的内容必须更改,则应该返回副本)
静态static关键字
可以将某个静态的东西看作是属于该类的东西,而不是属于该类的某个特定实例。
静态属性将在类的所有实例之间共享(因为它同样不属于实例)
在谈论静态方法时,该方法属于类,这意味着我们不需要一个实例来调用它(顺便说一句,同样适用于属性),不能使用关键字 super 和 this,因为和对象无关
静态块就像静态方法一样不能引用实例属性,也不能使用关键字 super 和 this。不能包含返回语句,如果该块不能正常完成(例如,由于未捕获的异常) ,那么这是一个编译时错误
最终final关键词
可以应用于变量、方法和类
- 变量
当 final 应用于变量时,不能在变量初始化后更改其值。这些变量可以是属性(静态和非静态)或参数。Final 属性既可以在声明时初始化,也可以在构造函数中初始化,或者在初始化器块中初始化。
变量只能初始化一次,之后不能更改其值
public class Example {
private final int number = 0;
private final String name;
private final int total;
private final int id;
{
name = "Name";
}
public Example() {
number = 1; // Compile-time error
total = 10;
}
public void exampleMethod(final int id) {
id = 5; // Compile-time error
this.id = id; // Compile-time error
}
}
- 方法
当 final 应用于某个方法时,不能重写它。 - 类
当 final 应用到一个类时,不能子类化
Comments | 0 条评论