高级类特性2
一.关键字: static与类变量,类方法
1.类变量、类方法
类变量、类方法设计思想:
- 类属性作为该类对象间共享的变量, 在设计类时, 分析哪些属性不因对象的不同而改变, 将这些属性设置为类属性。相应的方法设置为类方法。
- 如果方法与调用者无关, 则通常声明为类方法, 由于不需要创建对象就可以调用类方法(用类名.方法名调用), 从而简化了方法的调用。
(类变量可以被所有实例化对象共享,使用时应慎重)
(类方法常用于工具类的创建)
public class Test {
//静态方法:一般用于创建工具类,比如:判断字符串是不是空字符串
public static boolean isEmpty(String s) {
boolean flag = true;
if(s != null && s != "") {
flag = false;
}
return flag;
}
}
2.static关键字
使用范围:
- 在java类中, 可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员, 被所有对象所共享
- 访问权限允许时, 可不创建对象, 直接被类调用
在static方法内部只能访问static类的staic属性, 不能访问类的非static属性
不需要实例就能访问static方法, 因此static方法内部不能有this, super
重载的方法需要同时为static或非static的
3.单例(Singleton)设计模式-饿汉式、懒汉式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
单例设计模式:就是采取一定的方法保证在整个软件系统中, 对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法。
如果要让类在一个虚拟机中只能产生一个对象, 首先必须将类的构造方法的访问权限设置为private, 保证在类的外部无法产生类的对象, 只能调用该类的某个静态方法以返回类内部创建的对象。
静态方法只能访问类中的静态成员变量。所以, 指向类内部产生的该类对象的变量也必须定义成静态的。
适用于: 当这个实例化对象的创建需要占用大量资源, 耗时长
1.饿汉式
一开始直接内部创建一个私有的(private)的静态对象(缺点:可能会浪费内存)
将构造器私有化, 确保外部无法创建该类对象
同时创建一个public的getInstance()方法用于获得该单例对象
/**
* 饿汉式的单例模式
* @author chenyao
*/
public class Single {
//私有化构造器,确保类的外部不能用new创建新的对象
private Single() {
}
private static Single single = new Single();//内部创建一个私有的Single类对象
public static Single getInstance() {//创建getInstance方法来返回这个Single类对象
return single;
}
}
Single s = Single.getInstance();//调用getInstance()来获得该单例对象
2.懒汉式
最开始, 对象是null, 第一次被调用时创建一个对象, 之后被调用的都是第一次创建的对象
/**
* 懒汉式的单例模式
* @author chenyao
*/
public class Single1 {
//先私有化构造方法
private Single1() {
}
private static Single1 s1 = null;
public static Single1 getInstance() {
if(s1 == null) {//第一次被调用时,s1==null,会创建一个新的Single1类对象
s1 = new Single1();
}
return s1;//之后被调用时,直接retuan s1,即第一次被创建的Single1类对象
}
}
懒汉式存在线程安全问题, 多个线程可能同时调用这个方法,同时判断这个对象还没有被创建出来,就各自创建一个实例。
在多线程章节中, 可以使用synchronized关键字解决该问题
4.扩展-理解main方法的语法
public static void main(String[] args){
}
java虚拟机需要调用类的main()方法, 所以该方法访问权限必须是public,
java虚拟机在执行main()方法时不必创建对象, 所以该方法必须是static的,
该方法接收一个String类型的数组参数, 该数组中保存执行java命令时传递给运行的类的参数
二.类的成员之四: 初始化块
1.重温: java程序初始化顺序
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Parent {
static private Pattern pattern = Pattern.compile("(\\D+)(\\d+)");
static private int count=0;
private static String str = "父类静态属性1";
static {
Parent.println(str);
Parent.println("父类静态块2");
}
{//父类块1
Parent.println(getParentString());
Parent.println(getSubString());
}
private String string = "父类属性7";
{//父类块2
Parent.println(string);
Parent.println("父类块8");
}
private boolean result=true;
public Parent() {
super();
Parent.println("父类构造器9");
}
public String getSubString() {
return null;
}
public String getParentString() {//boolean默认值为false
return !result ? "父类属性默认值5" : string;
}
public static void println(String out) {
Matcher matcher = pattern.matcher(out);
if (matcher.find()) {
System.out.println(String.format("%02d、%7s %02d",++count,matcher.group(1),
Integer.valueOf(matcher.group(2))).replaceAll(" ", " "));
}
}
}
public class Test extends Parent {
private static String str = "子类静态属性3";
static {
Parent.println(str);
Parent.println("子类静态块4");
}
private String string = "子类属性10";
{
Parent.println(string);
Parent.println("子类块11");
}
private boolean result=true;
public Test() {
super();
Parent.println("子类构造器12");
}
public String getSubString() {
return !result ? "子类属性默认值6" : string;
}
public static void main(String[] args) {
new Test();
}
}
01、父类静态属性 01
02、父类静态块 02
03、子类静态属性 03
04、子类静态块 04
05、父类属性默认值 05(为实例分配空间设置成默认值例如int=0,boolean=false,对象设置成null)
06、 子类属性默认值 06(为实例分配空间设置成默认值例如int=0,boolean=false,对象设置成null)
07、父类属性 07
08、父类块 08
09、父类构造器 09
10、子类属性 10
11、子类块 11
12、子类构造器 12
以下图片引自@ 安_shuai@https://blog.csdn.net/xyajia
2.类的成员之四: 初始化块(代码块)
作用:
- 对java对象进行初始化
- 程序的执行熟顺序: 声明成员变量的默认值→显示初始化、多个程序块依次被执行→构造器再对成员进行赋值操作
1.非静态代码块
{
代码;
…
}
- 可以有输出语句
- 可以对类的属性声明进行初始化操作
- 可以调用静态或非静态的变量和方法
- 若有多个非静态代码块, 按从上到下的顺序执行
- 每次创建对象的时候都会执行, 优先于构造器执行
2.静态代码块
静态内容总是优先于非静态内容执行, 静态代码块优先于构造方法执行
静态代码块属于类加载过程, 当第一次用到本类时, 静态代码块执行唯一的一次。
static String s;
static{
//这里只能使用静态修饰的属性和方法
system.out.pringln(s);
…;
}
- 可以有输出语句
- 可以对类的属性声明进行初始化操作
- 只能调用静态的属性和方法
- 静态代码块执行优先于非静态代码块
- 若有多个静态代码块, 按从上到下的顺序执行
- 多次创建对象时, 静态代码块只执行一次
区别:
- 非静态代码块, 每创建一个对象执行一次
- 静态代码块只会执行一次
作用:
- 实际开发中, 静态代码块使用较多, 用在初始化类的静态属性
- 在匿名内部类中, 用代码块代替构造方法对类属性进行初始化
/**
* 在匿名内部类中, 用代码块代替构造方法
*/
Chinese c = new Chinese();
Chinese c1 = new Chinese() {//大括号内是一个Chinese的匿名子类,可以重写Chinese的showLanguage()方法
//现在要把其父类也就是Person类对象c1的language属性改成"中文",但不想更改Chinese类的代码
{//在匿名内部类中,用代码块代替构造方法
super.language = "中文";
}
@Override
public void showLanguage() {
System.out.println("=========");
super.showLanguage();
}
};//匿名内部类,该 创建对象语句仍然以分号结尾
c.showLanguage();
c1.showLanguage();
三.关键字:final
在java中声明类、属性和方法时, 用final修饰表示"最终"。
-
final标记的类不能被继承。(提高安全性, 提高程序的可读性)
String类、System类、StringBuffer类 -
final标记的方法不能被子类重写。
Object类中的getClass() -
final标记的变量(成员变量)即称为常量, 只能被赋值一次, 一旦赋值, 不可更改。常量名称大写, 连接采用"_", 如"NAME_1"。
final标记的成员变量必须在声明的同时或在每个构造方法中或代码块中显示赋值, 然后才能使用。
常量包括 final修饰的成员变量(实例变量)和静态变量(静态变量也只能是用static修饰的成员变量), 那么用final修饰的局部变量(方法内)我们也可以称之为不可变变量。(存储在栈中)
四.抽象类(abstract class)
继承层次中一个个新子类的定义让类更加具体, 父类则更加一般通用。
类的设计应该保证父类和子类存在共享特征。
有时将一个父类设计得非常抽象, 以至于它没有具体的实例, 这样的类叫做抽象类。
1.abstract关键字
-
用abstract修饰一个类时, 这个类叫做抽象类;
-
用abstract修饰一个方法时, 该方法才叫做抽象方法。
抽象方法: 只有方法的声明, 没有方法的实现。以分号结束
abstract int abstractMethod( int a ); -
含有抽象方法的类必须被声明为抽象类
-
抽象类不能被实例化。抽象类是作为父类用来被继承的, 抽象类的子类必须重写父类的抽象方法, 并提供方法体。
-
若子类没有重写全部的抽象方法, 仍为抽象类
-
不能用abstract修饰属性、私有方法、静态方法、构造器、final的方法。
应用:
抽象类是用来模拟那些父类无法全部实现, 而是由子类提供具体实现的对象的类。
2.模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板式的设计, 抽象类作为多个子类的通用模板, 子类在抽象类的基础上进行扩展、改造, 但子类总体上会保留抽象类的行为方式。
应用场景:
- 当功能内部一部分实现是确定, 一部分实现是不确定的。 这时可以把不确定的部分暴露出去,让子类去实现。
- 编写一个抽象父类, 父类提供了多个子类的通用方法, 并把一个或多个方法留给其子类实现, 就是一种模板模式。
public abstract class Template {
public abstract void code();//抽象方法code(),留给子类实现
public final void getTime() {
long start = System.currentTimeMillis();//返回当前秒数
code();
//执行code()代码
long end = System.currentTimeMillis();//返回当前秒数
System.out.println("code方法执行的时间:"+(end-start));
}
public static void main(String[] args) {
new Test().getTime();
}
}
class Test extends Template {
@Override
public void code() {//子类中重写父类code()方法,对模板进行实现
int sum = 0;
for(int i=0;i<100000;i++) {
sum += i;
}
}
}
五.更彻底的抽象:接口(interface)
- 有时需要从多个类中派生出一个子类, 继承多个类的所有属性和方法,
- 但是java不支持多重继承。使用接口可以达到多重继承的效果。
- 接口(interface)是抽象方法和常量值的定义的集合。
- 从本质上, 接口是一种特殊的抽象类, 这种抽象类中只包含常量和方法的定义, 而没有变量和方法的实现。
1.关键字interface
实现接口类:
class Subclass implements InterfaceA{}
一个类可以实现多个无关的接口
接口也可以继承其他接口
与继承关系类似, 接口与实现类之间存在多态性
特点:
- 用interface定义
- 接口中所有成员变量都默认 且必须是由 public static final 修饰的
- 接口中所有方法都默认是由 public abstract 修饰的
- 接口没有构造器
- 接口采用多层继承机制
public interface TestIn {
int ID = 1;//接口中变量默认由public static final修饰,为常量
void test();//接口中方法默认由public abstract修饰,为抽象方法
}
子类继承父类, 只能继承一个父类。但是一个类可以实现多个接口, 多个接口之间用","分隔
/**
* 子类继承父类,只能继承一个父类
* 但是一个类可以实现多个接口, 多个接口之间用","分隔
* @author chenyao
*/
public class TestInimpl implements TestIn, TestIn1 {//创建TestInimpl类实现TestIn接口
@Override
public void test() {//实现接口TestIn,重写接口的抽象方法
}
@Override
public void test1() {
}
}
如果一个类既继承父类, 又实现接口, 那么需要先继承, 后实现
/**
1. 如果一个类既继承父类, 又实现接口
2. 那么先继承, 后实现
3. @author chenyao
*/
public class TestInimpl2 extends Animal implements TestIn {
@Override
public void test() {//重写TestIn类的abstract抽象方法
// TODO Auto-generated method stub
}
@Override
public void move() {//重写Animal类的abstract抽象方法
// TODO Auto-generated method stub
}
}
**接口的多态性:**体现对象的多态
//通过实现类接收对象
Teacher t = new Teacher();
//通过接口接收对象
Cooking c = new Teacher();
c.fry();
Singing s = new Teacher();
//接口接收的对象(如c)只能重写调用接口里定义的方法,不能调用实现类、父类中的方法
应用场景:
- 通过接口实现父类中增加抽象方法的需求
有需求在父类中新增一个抽象方法时, 可以通过新建接口写入新增的抽象方法, 子类根据需要通过新建接口进行实现。
父类新增抽象方法时,所有子类都需要重写其抽象方法,否则需要定义为抽象类,且不能被实例化。
父类需要稳定的抽象。
通过新建接口的形式, 避免对父类和其子类的改动。
- 通过多个接口实现子类的多种功能(抽象方法)
通过继承只能继承单个父类的属性和方法
抽象类与接口的区别:
- 抽象类是对一类事物的高度抽象, 其中既有属性也有方法
- 接口是对一系列动作的抽象, 即对方法的抽象
2.工厂方法(FactoryMethod)和代理模式(Proxy)
FactoryMethod模式是设计模式中引用最为广泛的模式, 在面向对象的编程中, 对象的创建工作非常简单, 对象的创建时机却很重要。
FactoryMethod通过面向对象的手法, 将所要创建的具体对象的创建工作延展到了子类, 从而提供了一种扩展的策略, 较好的解决了这种紧耦合的关系。
- 构建一个要创建对象的类的接口(BMW{})
/**
* 宝马车的产品接口
* @author chenyao
*/
public interface BMW {
//关于宝马车类的属性
//产品信息
//发动机属性等
void showInfo();
}
/**
* 构件具体的车的类,实现BMW接口的抽象方法
* @author chenyao
*/
class BMW3i implements BMW{
@Override
public void showInfo() {
System.out.println("这是宝马3系车");
}
}
class BMW5 implements BMW{
@Override
public void showInfo() {
System.out.println("这是宝马5系车");
}
}
class BMW7 implements BMW{
@Override
public void showInfo() {
System.out.println("这是宝马7系车");
}
}
- 构建一个包含创建对象的方法的接口(BMWFactory{})
/**
* 汽车生产工厂接口
* @author chenyao
*/
public interface BMWFactory {
public BMW productBWM();//构建生产BMW类的抽象方法productBWM(),将其结果返回给BMW接口
//理解此处语法类似 public String showInfo(); 此处BMW为返回值类型,即BMW接口
}
/**
* 根据具体车的类,构建具体的工厂类,实现工厂的抽象方法(生产)
* @author chenyao
*/
class BMW3Factory implements BMWFactory{
@Override
public BMW productBWM() {//此处重写BMW productBWM()方法
System.out.println("生产宝马3系车");
System.out.println("改造宝马3系车,新型号BMW3i");
return new BMW3i();//实例化new一个BMW3i()对象,将其返回给BMW接口
}
}
class BMW5Factory implements BMWFactory{
@Override
public BMW productBWM() {
System.out.println("生产宝马5系车");
return new BMW5();
}
}
class BMW7Factory implements BMWFactory{
@Override
public BMW productBWM() {
System.out.println("生产宝马7系车");
return new BMW7();
}
}
- 开发人员B的工作: 通过方法(工厂)接口创建对象, 通过类(产品)接口访问对象属性
public class Test {
public static void main(String[] args) {
//这是开发人员B的工作
BMW b3 = new BMW3Factory().productBWM();
b3.showInfo();
BMW b5 = new BMW5Factory().productBWM();
b5.showInfo();
BMW b7 = new BMW7Factory().productBWM();
b7.showInfo();
}
}
其意义在于: 通过工厂将new对象隔离, 通过产品接口可以接收不同实际产品的实现类, 实例的类名的改变不影响其他合作开发人员的编程。
3.扩展-接口中的default/static方法(非abstract)
修饰方法: default 只能被实例对象调用; static 可以被类调用; abstract 不能被类或实例调用,三个修饰符相互冲突
从Java 8开始, 接口中允许定义普通方法 [public] default 方法, default替换abstract (public可以省略)
应用场景
-
接口中的默认方法, 可以解决接口升级的问题(接口如果增加抽象方法,所有实现类都需要重写)
可以增加接口中的默认方法, 默认方法会被实现类继承,可重写,也可不重写 -
*备注: 后面学习Lambda表达式,函数式编程时, 接口的默认方法可以拼接函数模型
从Java 8开始, 接口中也允许定义静态方法 [public] static 方法 , static替换abstract
- 接口中的静态方法应该通过接口名直接调用。
- **注意: 不能通过接口实现类的对象来调用接口当中的静态方法。**一个实现类可以实现多个接口, 因为可能产生冲突。
4.扩展-接口中的私有方法
问题描述:
需要抽取去一个共有方法, 用来解决两个默认方法之间重复代码的问题
但是这个共有方法不应该让实现类使用, 应该是私有化的
解决方案:
从Java 9开始, 接口中允许定义私有方法。
- 普通私有方法: 解决多个普通方法之间重复代码问题
格式:
private 返回值类型 方法名称(参数列表){
方法体
} - 静态私有方法: 解决多个静态方法之间重复代码问题
格式:
private static 返回值类型 方法名称(参数列表){
方法体
}
5.扩展-接口中的常量
常量是用final修饰的成员变量, 一旦赋值, 不可更改
常量包括 final修饰的成员变量(实例变量)和静态变量(静态变量也只能是用static修饰的成员变量), 那么用final修饰的局部变量(方法内)我们也可以称之为不可变变量。(存储在栈中)
接口中定义的"成员变量", 必须用 public static final 修饰, 其实是个常量。
格式 : public static final 数据类型 常量名称 = 数据值;
接口的功能是对外扩展, 所以不能用private修饰成员属性
接口不能被实例化, 所以其成员属性必须由接口调用, 用static修饰
接口是用来扩展功能的,定义抽象方法,其成员属性不应该被继承。需要继承属性应该继承父类/抽象类。
- 接口中的常量可以省略 public static final ,不写也默认由 public static final 修饰
- 接口中的常量, 必须进行赋值, 不能不赋值
- 常量名称全部大写,使用下划线_分隔
六.扩展-使用多态写法的好处
等号,左边编译,右边运行
好处: 不论等号右边new创建的是哪个子类的对象, 等号左边调用方法都不会变化。具有统一性。
向上转型一定是安全的。
七.类的成员之五: 内部类(Inner class)
- 在java中, 允许在一个类的内部再定义一个类, 前者为外部类, 后者为内部类
- Inner class一般用在定义它的类或语句块之内, 在外部引用它时必须给出完整的名称。
Inner class 的名字不能与包含它的类名相同 - Inner class 可以使用外部类的私有数据, 因为它是外部类的成员, 同一个类的成员之间可以相互访问。
- 外部类访问内部类中的成员需要: 内部类.成员 或 内部类对象.成员。
- 分类:
1.成员内部类 (static成员内部类和非static成员内部类)
2.局部内部类 (不谈修饰符)、匿名内部类
内部类特性:
Inner class 作为外部类的成员, 有如下特性:
- 可以声明为final的
- 和外部类不同, Inner class 可以声明为private或者protected;
- Inner class 可以声明为static的, 但此时就不能再使用外层类的非static变量;
Inner class 作为类, 有如下特性:
- 可以声明为abstract类, 因此可以被其他内部类继承
非static的内部类中的成员 不能声明为static的, 只有在外部类或static的内部类中才可声明为static成员。
应用场景:
内部类主要解决java不能多重继承的问题
/**
* 类A同时获得类B和类C的方法
* 通过内部类变相实现多重继承,同时继承多个类
* 通过内部类的匿名对象调用其继承的属性或方法
* @author chenyao
*/
public class TestInner {
public static void main(String[] args) {
A a = new A();
a.testB();
a.testC();
}
}
class A{
public void testB() {
new InnerB().testB();//创建匿名的内部类对象调用内部类重写的父类方法
}
public void testC() {
new InnerC().testC();
}
public class InnerB extends B{//class A的内部类InnerB继承class B
@Override
public void testB() {
System.out.println("这是重写后的testB方法");;
}
}
public class InnerC extends C{//class A的内部类InnerC继承class C
@Override
public void testC() {
System.out.println("这是重写后的testC方法");;
}
}
}
class B{
public void testB() {
}
}
class C{
public void testC() {
}
}