目录
多态的应用:模板方法设计模式(TemplateMethod)
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。要么一起使用。其中两个找错误的面试问题
Java 8 中关于接口的改进(为接口添加静态方法和默认方法)
关键字static
说明:
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。
例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量
static 属性
- static:静态的
- static 可以用来修饰:属性、方法(抽象方法除外)、代码块、内部类
- 使用 static 修饰属性,就变成了静态变量(或类变量)。
- 属性:是否使用 static 修饰,又分为:静态属性 VS 非静态属性(实例变量)
- 静态属性:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过静态变量去修改某一个变量时,会导致其他对象调用此静态变量时,是修改过的
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有了一套类中的非静态属性。当修改其中一个非静态属性时,不会导致其他对象中同样的属性值的修饰
- 类变量说明
- 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
- 静态变量的加载要早于对象的创建
- 由于类只会加载一次,则静态变量在内存中也只会存在一次。存在方法区的静态域中(现在叫元空间了)
- 典型的静态属性举例:System.out.println() #打印到控制台
- 属性:是否使用 static 修饰,又分为:静态属性 VS 非静态属性(实例变量)
- 判断属性是否要声明static
- 属性是否要被所有对象共享
- 类中常量经常要声明成static
static 方法
- 随着类的加载而加载,可以通过"类.静态方法"的方式调用
- 注意!!
- 静态方法中,只能调用静态的方法或静态的属性,例如main方法就是静态方法
- 非静态的方法中,可以调用所有的方法或属性
- 在静态的方法内,不能使用 this 关键字、super 关键字
- 关于静态属性和静态方法的使用,大家从生命周期的角度去理解。
- 判断方法是否要声明成static
- 操作静态属性的方法,通常设置为 static (普通方法也可以操作static属性,因此不强求)
- 设置好的工具类方法.比如之前设置的Array工具类,它的方法为了方便调用我们可以把里面的设置成静态,然后通过"类名.方法名"调用就能实现自己想要的功能
提示:被static修饰方法被子类继承,但是子类不能重写静态方法,硬要写就是覆盖。而抽象方法本身没有实现,就是用来给子类继承,存在内存中的位置也不是一样的。用static修饰就不能被子类继承了(这里抽象方法后面会再次强调)
package javase8;
public class OOP1 {
public static void main(String[] args) {
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
c1.setIdentityCard(123456);
c1.setName("小明");
System.out.println(c1);//Chinese{name='小明', identityCard=123456, nation=中国}
c2.setIdentityCard(654321);
c2.setName("小红");
Chinese.setNation("China");//调用静态方法为静态属性设值,静态资源时类资源,只有一份,因此别的对象改了另一个对象也能读取到
System.out.println(c2);//Chinese{name='小红', identityCard=654321, nation=China}
}
}
class Chinese{
private static String nation="中国";
private String name;
private Integer identityCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getIdentityCard() {
return identityCard;
}
public void setIdentityCard(Integer identityCard) {
this.identityCard = identityCard;
}
public static String getNation() {//静态方法中,只能调用静态的方法或静态的属性
return nation;
}
public static void setNation(String nation) {//静态方法中,只能调用静态的方法或静态的属性
Chinese.nation = nation;
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", identityCard=" + identityCard +
", nation=" + nation +
'}';
}
}
Java设计模式之一:单例(Singleton)设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱、”套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为 private,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
单例模式的饿汉式与懒汉式
- 饿汉式:
- 对象没使用就已经加载出来了,等待指针指向他就可以了
- 好处:饿汉式是线程安全的
- 坏处:对象加载时间过长
- 懒汉式:
- 对象只有使用的时候才会创建
- 好处:对象延迟创阿金
- 目前的写法会导致线程不安全,后面提及多线程会优化写法
package javase8;
public class OOP2 {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println(singleton1==singleton2);//true.是同一个对象
}
}
//单例的饿汉式
//class Singleton{
//
// private Singleton(){//私有化构造器,让外部不能通过构造器创建对象
// }
//
// private static Singleton s1 = new Singleton();//内部使用构造器创建一个对象,并且私有化不让外部获取,设值静态化,确保在内存中资源只有一份
//
// public static Singleton getSingleton(){//创建公开的获取对象的方法,这里设置静态方法除了方便调用,还有配合mainn方法使用
// return s1;
// }
//}
//单例的懒汉式
class Singleton{
private Singleton(){//私有化构造器,让外部不能通过构造器创建对象
}
private static Singleton s1;//设值静态化,确保在内存中资源只有一份
public static Singleton getSingleton(){//创建公开的获取对象的方法,这里设置静态方法除了方便调用,还有配合mainn方法使用
if(s1==null){
s1 = new Singleton();
return s1;
}
return s1;
}
}
- 单例模式优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以采用单例的饿汉式,可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式使用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
Application也是单例的典型应用
Windows 的 **Task Manager (任务管理器)**就是很典型的单例模式
Windows 的 **Recycle Bin(回收站)**也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站的计数器,一般也是单例模式实现,否则难以同步。
(了解)mian方法
说明:
由于 Java 虚拟机需要调用类的 main()方法,所以该方法的访问权限必须是 public,又因为 Java 虚拟机在执行 main()方法时不必创建对象,所以该方法必须是 static 的,该方法接收一个 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。
又因为 main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
- main()方法作为程序的入口;
- main()方法也是一个普通的静态方法
- main()方法也可以作为我们与控制台交互的方式。(之前,使用 Scanner)
类成员之一:代码块{}
- 用于初始化类、对象
- 代码块修饰只能用static
- 代码块分为静态代码块和非静态代码块
- 静态代码块
- 内部可以有输出语句
- 随着类加载而加载,只执行一次
- 作用是初始化类信息
- 如果一个类定义多个静态代码块,执行则从上到下
- 静态代码块的执行优先于非静态代码块的执行
- 静态代码块只能执行静态属性和静态方法
- 非静态代码块:
- 内部可以有输出语句
- 随着对象创建而创建,因此每创建一个对象,就执行一次非静态代码块。
- 作用是可以在创建对象时,对象的属性等进行初始化。
- 如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
对属性可以赋值的位置:
- ①默认初始化
- ②显式初始化 / ⑤在代码块中赋值
- ③构造器中初始化
- ④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值。
- 执行的先后顺序:① - ② / ⑤ - ③ - ④
package javase8;
public class OOP3 {
public static void main(String[] args) {
Leaf leaf = new Leaf();
/*
Root 的静态初始化块
Mid 的静态初始化块
Leaf 的静态初始化块
Root 的普通初始化块
Root 的无参数的构造器
Mid 的普通初始化块
Mid 的无参数的构造器
Mid 的带参数构造器,其参数值:参数
Leaf 的普通初始化块
Leaf 的构造器
*/
System.out.println(leaf.orderId);//4
}
}
//爷类
class Root {
static {
System.out.println("Root 的静态初始化块");
}
{
System.out.println("Root 的普通初始化块");
}
public Root() {
System.out.println("Root 的无参数的构造器");
}
}
class Mid extends Root {
static {
System.out.println("Mid 的静态初始化块");
}
{
System.out.println("Mid 的普通初始化块");
}
public Mid() {
System.out.println("Mid 的无参数的构造器");
}
public Mid(String msg) {
//通过 this 调用同一类中重载的构造器
this();
System.out.println("Mid 的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid {
static {
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf() {
//通过 super 调用父类中有一个字符串参数的构造器
super("参数");
System.out.println("Leaf 的构造器");
}
int orderId = 3;
{
orderId = 4;
}
}
关键字:final
- final:最终的
- final可以用来修饰的结构:类、方法、变量
- final用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类
- final修饰一个方法:final标记的方法不能被子类重写。比如:Object类中的getClass()。
- final用来修饰变量:此时的"变量"(成员变量或局部变量)就是一个常量。标识符要求全大写,单词之间用下划线分开,且只能被赋值一次。
- final修饰属性,初始化时声明和赋值必须再同一条代码完成,可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
- final修饰局部变量: 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
- static final 用来修饰:全局常量
package javase8;
public class OOP4 {
public static void main(String[] args) {
Other o = new Other();
new OOP4().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();
System.out.println(o.i++);//0
}
}
//父类被final修饰后不能被继承
//final class Father{}
//class Son extends Father{}
//方法被final修饰后不能被重写
//class Father{
// public final void eat(){}
//}
//class Son extends Father{
// @Override
// public void eat() {
// super.eat();
// }
//}
//final修饰变量后就是一个常量,标识符要全大写,单词之间用下划线分开,初始化时声明和赋值必须再同一条代码完成
class Father {
static final String sale = "男";//static final 用来修饰:全局常量
final String NAME = "父亲";//final修饰属性,初始化和赋值必须再同一条代码完成
public void run() {
final int SPPED = 10;
// SPPED = 6;//被final用来修饰变量后变量变成了常量,常量不能再次赋值
System.out.println("跑" + SPPED + "快");
}
public void salary(final int MONEY) {
// MONEY = 10;//final修饰形参时,表明此形参是一个常量。当我们调用此方法时给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,但不能重新赋值
}
//面试题1
// public int addOne(final int x) {
// return ++x; // 代码编译错误,不能给最终变量赋值,x++同理,(延伸)如果没有final修饰形参的话,如果输入1结果返回的结果就是1
// }
}
//面试题2
class Other {
public int i;
}
抽象类和抽象方法
说明:
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
简便来说就是:抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
- abstract:抽象的
- abstract 可以用来修饰的结构:类、方法
- abstract 修饰类-->抽象类
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化全过程),但是抽象类却不可以实例化
- 但在开发中,都会提供抽象类的子类,让子类对象实例化,实现相关的操作
- abstract 修饰方法-->抽象方法(钩子方法)
- 抽象方法,只有方法的声明,没有方法体。
- 包含抽象方法的类,一定是一个抽象类。但是抽象类中可以没有抽象方法
- 若子类重写了父类中所有的抽象方法,此子类可以是普通类.若重写抽象父类里面的抽象方法,如果只重写一个,必须声明为抽象类,否则编译错误
- abstract 不能用来修饰变量、代码块、构造器
- abstract 不能用来修饰私有方法、静态方法、final 的方法、final 的类
- 抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
- 名词解释:
- 抽象类和接口是不能直接new对象
- 匿名对象:匿名对象就是没有变量去存储这个实体对象。
- 匿名实现类:接口或抽象类没有专门的实现类去实现或者继承实现里面的方法,当我们没有为抽象类或者接口(后面会学接口)创建子类去使用时,但我们又要在遵循抽象类的规范去使用它里面的方法时,我们可以使用匿名实现类去实现里面的方法并且调用。具体做法是new接口或者抽象类括号右边使用{}直接重写接口或者抽象类的方法作为实现类,但是这个实体对象时没有具体的类的,因此用多态方式,也就是使用使用接口或者抽象类数据类型变量去接收这个匿名实现类。
package javase8;
public class OOP5 {
public static void main(String[] args) {
// new Person();//抽象类不允许实例化
// new Peasant();//与上同理
Officer officer = new Officer();//抽象类的构造方法,Officer类的构造方法
officer.eat();//今天要和工人们一起进餐
officer.say();//今天要去为人民服务
officer.setName("小刘");
System.out.println(officer.getName());//小刘
/*匿名对象:抽象类和接口是不能直接new对象,当我们没有为抽象类或者接口(后面会学接口)创建子类去使用时,
但我们又要在遵循抽象类的规范去使用它里面的方法时,我们可以使用匿名实现类去实现里面的方法并且调用*/
Person p1 = new Person(){//这里创建了一个匿名实现类的非匿名对象p1(多态)去实现抽象类里面的方法,记住这个是个类,因此需要全部抽象方法都实现
@Override
void say() {
System.out.println("我是匿名对象的say()方法");
}
@Override
void eat() {
System.out.println("我是匿名对象的eat()方法");
}
};
p1.eat();//我是匿名对象的eat()方法
p1.say();//我是匿名对象的say()方法
p1.setName("匿名");
System.out.println(p1.getName());//匿名
}
}
abstract class Person {//抽象类
//抽象类的构造方法
public Person() {
System.out.println("抽象类的构造方法");
}
//属性
private String name;
abstract void say();//抽象方法,包含抽象方法的类,一定是一个抽象类。但是抽象类中可以没有抽象方法
abstract void eat();
//抽象类也可以拥有普通方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
abstract class Peasant extends Person {
//重写抽象父类里面的抽象方法,如果只重写一个,必须声明为抽象类,否则编译错误
@Override
void say() {
System.out.println("今天收成不错");
}
}
class Officer extends Person {
public Officer() {
super();//抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化全过程),但是抽象类却不可以实例化
System.out.println("Officer类的构造方法");
}
//若子类重写了父类中所有的抽象方法,此子类可以是普通类
@Override
void say() {
System.out.println("今天要去为人民服务");
}
@Override
void eat() {
System.out.println("今天要和工人们一起进餐");
}
}
多态的应用:模板方法设计模式(TemplateMethod)
说明:
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式
package javase8;
import java.util.Arrays;
public class OOP6 {
public static void main(String[] args) {
int[] i = {6,3,6,9,8,7,9,5};
new BubbleSort().time(i);//[3, 5, 6, 6, 7, 8, 9, 9],运算完成使用的时间是:1毫秒
}
}
abstract class OperationTime {
public void time(int[] i) {
long start = System.currentTimeMillis();//java.lang提供的时间工具,从java创始到现在的毫秒时间,这里设置开始运算时间
code(i);//运算的方法,这个方法是可变的,由子类实现,所以是抽象方法
long stop = System.currentTimeMillis();//运算结束时间
System.out.println("运算完成使用的时间是:"+(stop-start)+"毫秒");
}
public abstract void code(int[] i);
}
class BubbleSort extends OperationTime{
int agency;
@Override
public void code(int[] i) {
for (int j = 0; j < i.length-1; j++) {
for (int k = 0; k <i.length-j-1 ; k++) {
if(i[k]>i[k+1]){
agency = i[k];
i[k]=i[k+1];
i[k+1]=agency;
}
}
}
System.out.print(Arrays.toString(i)+",");
}
}
-
模板方法设计模式常用地方
- 数据库访问的封装
- Junit 单元测试
- JavaWeb 的 Servlet 中关于 doGet/doPost 方法调用
- Hibernate 中模板程序
- Spring 中 JDBCTemlate、HibernateTemplate 等
接口(interface)
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就通过接口多继承实现多重继承的效果。
(补充)
- Java 中类的单继承性:一个类只能有一个父类
- Java中类的多重继承:一个父类有一个子类、这个子类又有子类、子类的子类又有子类......(后面提及到的接口就可以实现多重继承效果)
-
Java类可以多实现:一个类可以实现多个接口,接口可以使用","分开。
-
格式:class AA extends BB implementd CC,DD,EE
-
-
Java接口可以多继承:一个接口可以继承多个接口,接口可以使用","分开
-
Java类可以同时实现接口和继承类
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a(举例Dis-aB,意思就是D是B的子类) 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
- 接口使用 interface 来定义。
- 在 Java 中:接口和类是并列的两个结构
- JDK7 及以前,接口中的所有成员变量都默认是由 public static final 修饰的。但是书写中,可以省略不写。
- JDK7 及以前,接口中的所有抽象方法都默认是由 public abstract 修饰的。但是书写中,可以省略不写。
- 但是JDK1.8以后允许接口新增有默认修饰符修饰的方法(默认修饰符必须加上,以至于区分抽象方法)和静态修饰符修饰的方法(public可以省略不写是因为默认加上了,调用时就可以直接接口名.静态方法名())
- 接口中没有构造器,意味着接口不可以实例化。
- Java 开发中,接口通过让类去实现(implements)的方式来使用。 如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化,如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类,不能实例化。与抽象类一样。
- 接口的具体使用,体现多态性。以接口为引用类型,创建实现类对象。做到面向接口编程,实现规范编程。
- 面试题:抽象类与接口不同的地方
- 抽象类继承了Object父类,接口不继承Object父类
- 抽象类有构造器,意味着有构造方法,但是构造器是给子类使用的,而接口没有。但是他们两都不能实例化。
- 抽象类中可以有普通成员变量和常量,抽象类可以有抽象方法和普通方法。且抽象类的方法必须添加abstract修饰方法以区分抽象方法和普通方法
接口只能有静态常量和抽象方法,即使属性不写public static final 和抽象方法不写public abstract修饰默认也会自动加上,但是JDK1.8以后可以写入默认修饰符抽象方法(默认修饰符必须加上,以区分抽象方法,public可以省略不写是因为默认加上了)和静态方法(public可以省略不写是因为默认加上了,调用时就可以直接接口名.静态方法名()),下面的"Java 8 中关于接口的改进"会谈得更详细 - 抽象类可以有final或static修饰符修饰的普通方法,抽象类的方法接口因为都是抽象方法,且抽象方法不允许用final或static修饰抽象方法,因为被final修饰后的方法不能被重写,被static修饰的方法不能被继承,也就与抽象方法要实现的功能相违背(补充:final或static也不能修饰构造方法)
- 抽象类因为是类,所以只能单继承,多实现(类可以实现多个抽象类),多重继承(一个父类,一个子类,一个子类子类......)。接口可以多继承,一个接口继承多个接口
package javase8;
public class OOP7 {
// new Father1();//接口没有构造器
/*名词解释:
抽象类和接口是不能直接new对象
匿名对象:匿名对象就是没有变量去存储这个实体对象。
匿名实现类:接口或抽象类没有专门的实现类去实现或者继承实现里面的方法,
当我们没有为抽象类或者接口(后面会学接口)创建子类去使用时,但我们又要在遵循抽象类的规范去使用它里面的方法时,
我们可以使用匿名实现类去实现里面的方法并且调用。具体做法是new接口或者抽象类括号右边使用{}直接重写接口或者抽象类的方法作为实现类,
但是这个实体对象时没有具体的类的,因此用多态方式,也就是使用使用接口或者抽象类数据类型变量去接收这个匿名实现类。
*/
public static void main(String[] args) {
//这里创建了一个匿名实现类的匿名对象
new Father1() {
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void sleep() {
}
}.eat();//吃饭
//这里创建了一个匿名实现类非匿名对象f1
Father1 f1 = new Father1() {
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void sleep() {
System.out.println("睡觉");
}
};
f1.eat();//吃饭
f1.run();//跑步,这个方法是default修饰的,因此只能同包下使用
f1.sleep();//睡觉
}
}
//Java 中类的单继承性:一个类只能有一个父类
//class Father1{}
class Son1 extends Father1,Father{}//异常
//class Son1 extends Father1{}
//Java中类的多重继承:一个父类有一个子类、这个子类又有子类、子类的子类又有子类......
//class Father1{}
//class Son1 extends Father1{}
//class Son2 extends Son1{}
//class Son3 extends Son2{}
//多实现、多继承、同时实现接口和继承
//interface Father1{}
//interface Father2{}
//interface Father3{}
//class Father4{}
//class Son1 implements Father1,Father2,Father3{}//Java类可以多实现:一个类可以实现多个接口,接口可以使用","分开
//interface Father5 extends Father1,Father2,Father3{}//Java接口可以多继承:一个接口可以继承多个接口,接口可以使用","分开
//class Son2 extends Father4 implements Father1,Father2,Father3{}//Java类可以同时实现接口和继承类
interface Father1 {
public static final String name = "父亲";//public static final变灰了是因为默认加上可以省略不写,被final修饰的变量初始化时声明和赋值同时进行
public abstract void eat();// public abstract变灰了是因为默认加上可以省略不写
default void run() {
System.out.println("跑步");
}
;//JDK1.8后新特性,允许接口新增有默认修饰符修饰的方法(默认修饰符必须加上,以至于区分抽象方法)
public static void jump() {
}
;//JDK1.8后新特性,允许接口新增有静态修饰符修饰的方法,public变灰了是因为默认加上了可以省略不写,调用时就可以直接接口名.静态方法名()
// public Father1();//接口没有构造器
//设置抽象方法
void sleep();
}
abstract class Son1 implements Father1 {//当类实现接口但没有实现完全部抽象方法时,类必须为抽象类
@Override
public void eat() {
}
}
class Son2 implements Father1 {
@Override
public void eat() {
}
@Override
public void sleep() {
}
}
接口的应用:代理模式(Proxy)
代理模式是 Java 开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问
package javase8;
public class OOP8 {
public static void main(String[] args) {
Server s1 = new Server();
ProxyServer p1 = new ProxyServer(s1);
p1.browse();//测试代理类对被代理类方法进行拓展
/*
联网前的检查工作
真实的服务器来访网络
*/
}
}
//接口
interface NetWork{
public void browse();
}
//被代理类,就是被拓展的类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器来访网络");
}
}
//代理类,拓展被代理类
class ProxyServer implements NetWork{
private NetWork work;//接口变量
//含参构造方法,为接口变量赋值,采用多态方式
public ProxyServer(NetWork work){
this.work = work;
}
//为被代理设置拓展放啊
public void check(){
System.out.println("联网前的检查工作");
}
@Override
public void browse() {
check();//代理方法
work.browse();//被代理方法
}
}
代理应用场景
- 安全代理:屏蔽对真实角色的直接访问。不能直接访问被代理类访问,而是通过代理类来访问被代理类
- 远程代理:通过代理类处理远程方法调用(RMI)(Feign)后面会学到
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
- 举例:比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用 proxy 来进行大图片的打开。(了解即可)
代理模式的分类(后面会学)
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
- JDK 自带的动态代理,需要反射等知识
常见面试题:接口和抽象类之间的对比
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。要么一起使用。其中两个找错误的面试问题
package javase8;
public class OOP9 {
public static void main(String[] args) {
new Son3().print();
/*
* 1
* 0
* */
}
}
interface Father2{
int x = 0;
}
class Father3 {
int x = 1;
}
class Son3 extends Father3 implements Father2{
public void print(){
// System.out.println(x);//编译不通过,x的定义是模糊的
System.out.println(super.x);//等同于new Father3().x
System.out.println(Father2.x);
}
}
package javase8;
public class OOP10 {
public static void main(String[] args) {
new Ball("小明").play();//小明,PingPang
}
}
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball= new Ball("PingPang"); //这里是接口声明常量属性,可以省略了 public static final
}
class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name= name;
}
public void play() {
// ball = new Ball("Football"); //ball是常量,不能被重新赋值.
System.out.println(this.name);
System.out.println(ball.getName());
}
}
接口的应用:工厂模式
- 工厂是专门用来创建对象的
- 实现了创建者(new对象的过程)与调用者(拿着对象做什么)的分离,即将创建对象的具体过程屏蔽隔离 起来,达到提高灵活性的目的。
- 其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维 护,解决方式就是一个“分工”
- 工厂模式分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品, 需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无 能为力;支持增加产品族)
无工厂模式下(一个接口两个实现类,然后创建者和调用者结合)
package javase8;
public class OOP11 {
public static void main(String[] args) {
new Audi().run();//没有工厂创建对象,因此创建者和调用者结合
new BYD().run();//没有工厂创建对象,因此创建者和调用者结合
}
}
interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car {
public void run() {
System.out.println("比亚迪在跑");
}
}
简单工厂模式(在原有的OOP11代码更改)
- 简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的 参数的不同来返回不同的实例对象。
- 优点:实现了把调用者和创造者分离
- 缺点:对于增加新产品,不修改Factory类代码的话,是无法扩展的。违反了开闭原则(对扩展开放,对修改封闭。意思是你在别的地方可以对现有的代码进行拓展功能,但不能更改现有的代码)。
package javase8;
public class OOP11 {
public static void main(String[] args) {
//没有工厂创建对象,因此创建者和调用者结合
// new Audi().run();
// new BYD().run();
//有工厂创建对象
CarFactory.getAudi().run();
CarFactory.getByd().run();
CarFactory.getCar("比亚迪").run();
CarFactory.getCar("奥迪").run();
}
}
interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car {
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂类
class CarFactory {
//方式一
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null;
}
}
//方式二
public static Car getAudi() {
return new Audi();
}
public static Car getByd() {
return new BYD();
}
}
工厂方法模式
- 为了避免简单工厂模式的缺点,不完全满足 OCP(对扩展开放,对修改关闭)。 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担
- 在代码演示中,当我们需要增加汽车时,我们可以实现汽车接口、实现工厂来拓展,不需要修改原有的代码
package javase8;
public class OOP12 {
public static void main(String[] args) {
Car2 audi2 = new AudiFactory().getCar();
Car2 byd2 = new BydFactory().getCar();
audi2.run();//奥迪在跑
byd2.run();//比亚迪在跑
}
}
interface Car2 {
void run();
}
class Audi2 implements Car2 {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD2 implements Car2 {
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂接口
interface Factory {
Car2 getCar();
}
//两个工厂类
class AudiFactory implements Factory {
public Audi2 getCar() {
return new Audi2();
}
}
class BydFactory implements Factory {
public BYD2 getCar() {
return new BYD2();
}
}
简单工厂模式与工厂方法模式的总结
- 简单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。
- 在简单工厂模式中新产品的加入要修改工厂角色中的判断语句;
- 而在工厂方法模式中,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像工厂方法模式的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。
- 面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制——这在 Spring 中完美的体现了出来。(后面学到反射的时候会提及)
抽象工厂模式(略过)
- 抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上(意思是会把类分类得更加细化,并且类之间的小部分也会与别的类形成族。例如车根据品牌分类,每个品牌下车又有不同的作用,有跑车有商务车,那么品牌车之间的跑车又会形成一个族......)。而且 抽象工厂模式是三个里面最为抽象、最具一般性的。
- 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品 对象。
- 而且使用抽象工厂模式还要满足一下条件:
- 1) 系统中有多个产品族,而系统一次只可能消费其中一族产品。
- 2) 同属于同一个产品族的产品以其使用。 看过了前两个模式,对这个模式各个角色之间的协调情况应该心里有个数了,我 就不举具体的例子了。
Java 8 中关于接口的改进(为接口添加静态方法和默认方法)
- 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常相互一起使用类中的静态方法。你可以在标准库中找到像 Collection(集合接口)/Collections(集合的工具类,有很多操作集合的静态方法) ,也就意味着JDK1.8以后,Collection完全可以作为之前Collection和Collections的集合体,逐渐取代Collections。或者 Path/Paths 这样成对的接口和类
- 接口中的静态方法只能通过“接口.静态方法”去调用
- 被static修饰的方法无法被重写,只能被继承。硬要写的话就是隐藏掉原有父类的静态方法
- 默认方法:默认方法使用default关键字修饰。可以通过实现类对象来调用。我们在己有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如: java 8 API中对Collection, List、 Comparator等接口提供了丰富的默认方法。
- 接口中的默认方法可以通过创建实现类的对象,然后调用默认方法。语法:“实现类对象.默认方法”
- 接口中的默认方法是可以被重写的
- 类优先原则:子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
且子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。 - 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-->接口冲突。这就需要我们必须在实现类中重写此方法
- 在子类(或实现类)的方法中调用父类(或接口中)被重写(或实现)的方法
- 调用父类方法语法“super.方法名();”
- 调用接口方法语法"CompareA.super.方法名();"
package javase8;
public class OOP13 {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();//接口中的静态方法只能通过“接口.静态方法”去调用
// SubClass.method1();//与上同理
CompareA.method1();//CompareA:西安,正确调用接口静态方法
s.method2();//SubClass:上海,接口中的默认方法可以通过创建实现类的对象,然后调用默认方法。语法:“实现类对象.默认方法”
/* 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则*/
// s.method3();//SuperClass:北京
/*如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
这就需要我们必须在实现类中重写此方法*/
s.method3();
}
}
interface CompareA {
//静态方法
public static void method1() {
System.out.println("CompareA:西安");
}
//默认方法
public default void method2() {
System.out.println("CompareA:深圳");
}
default void method3() {
System.out.println("CompareA:杭州");
}
}
class SuperClass {
public void method3() {
System.out.println("SuperClass:北京");
}
}
interface CompareB {
default void method3() {
System.out.println("CompareB:上海");
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
//接口中的默认方法是可以被重写的
@Override
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
// 知识点 5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //调用自己定义的重写的方法
super.method3(); //调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
类的内部成员之五:内部类
说明:当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类
- 举例:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类.
- 内部类的分类:成员内部类 VS 局部内部类(方法内、代码块内、构造器内)
- 成员内部类
- 作为外部类的成员
- 调用外部类的结构,语法:"父类类名.this.方法名()"或"父类类名.this.外部类的属性"
- 可以被static修饰,非静态方法不能被静态内部类引用
- 可以被4种不同的权限修饰
- 作为类
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以abstract修饰成抽象类
- 实例化成员内部类
- 成员内部类中区分调用外部类的结构,语法:
- 变量名-->内部类的属性、内部类的局部变量
- this.变量名--内部类的属性
- 外部类名.this.外部类的属性-->外部类的属性
- 作为外部类的成员
成员内部类
package javase8;
public class OOP14 {//创建Dog实例(静态的成员内部类)
public static void main(String[] args) {
//创建Dog实例(静态的内部类)
Person1.Dog dog = new Person1.Dog();
dog.show();//调用静态内部类的方法
//创建Bird实例(非静态内部类)
// Person1.Bird bird = new Person1.Bird();//异常,不是静态内部类
//方法1
Person1 p = new Person1();//先创建外部类对象
Person1.Bird bird = p.new Bird();//在通过外部类创建内部类对象
bird.sing();//调用非静态内部类的方法
//方法2
new Person1().new Bird().display("喜鹊");//调用非静态内部类的匿名对象的display()方法
}
}
//外部Person1类
class Person1 {
String name = "李雷";
int age;
public void eat() {
System.out.println("人,吃饭");
}
//外部类成员的静态内部类
static class Dog {
String name;
int age;
public void show() {
System.out.println("卡拉是条狗");
// eat();//非静态方法不能再静态内部类引用
}
}
//非静态成员内部类
class Bird {
String name = "杜鹃";
public Bird() {
}
public void sing() {
System.out.println("我是一只猫头鹰");
Person1.this.eat();//调用外部类的非静态属性,固定写法
eat();
System.out.println(age);
}
public void display(String name) {
System.out.println(name); //方法的形参
System.out.println(this.name); //内部类的属性
System.out.println(Person1.this.name); //外部类的属性
}
}
public void method() {
//方法里的局部内部类(这种函数内写内部类非常少见开发少见)
class AA {}
}
{
//非静态代码块里面的局部内部类
class BB {}
}
public Person1() {
//构造器内的局部内部类
class CC {}
}
}
开发中局部内部类的使用
注意:在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
- jdk 7及之前版本:要求此局部变量显式的声明为final的
- jdk 8及之后的版本:可以省略final的声明
package javase8;
public class OOP15 {
public static void main(String[] args) {
Comparable comparable = new InnerClassTest1().getComparable();//获取Comparable接口匿名实现类非匿名对象
}
}
class InnerClassTest1 {
//开发中很少见函数内有内部类
// public void method(){
// //局部内部类
// class AA{}
// }
// 返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
//
// }
//
// return new MyComparable();//返回实现类的对象
//方式二:
return new Comparable(){//创建一个匿名实现类实现Comparable接口
@Override
public int compareTo(Object o) {
return 0;
}
};
}
public void method(){
//局部变量
int num = 10;//局部变量在被内部类访问时是需要被final修饰的,但是JDK8.0以后可以省略声明
class AA{
public void show(){
// num = 20; //局部变量被内部类访问是要是被final修饰的,因此不能修改
System.out.println(num);//内部访问局部变量
}
}
}
}
匿名内部类(也就是我们之前说的匿名实现类)
- 匿名内部类不能使用"static"修饰的成员,也就是不能有静态成员,只能创建一个匿名内部类的实例
- 一个匿名内部类一定是在new的后面,其作用是实现一个接口或者实现一个抽象类
- 语法格式
new 父类构造器(形参列表)或实现接口(){ //匿名内部类的类体部分 }
-
匿名内部类只能有一个对象
-
匿名内部类唯一的对象要用其父类或者接口类型的引用变量接收,形成多态
package javase8;
public class OOP16 {
public static void main(String[] args) {
AnonymousTest ta = new AnonymousTest();
//调用test方法时,需要传入一个Product参数,
//此处传入其匿名实现类的实例
ta.test(new Product(){
public double getPrice(){
return 567.8;
}
public String getName(){
return "AGP显卡";
}
});
}
}
interface Product{
public double getPrice();
public String getName();
}
class AnonymousTest {
public void test(Product p) {
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
}
静态内部类实例化结论
- 静态内部类中的非静态变量和方法需要实例化静态内部后,才能调用。(且访问权限够)
- 静态内部类中的静态变量和方法可以直接访问使用。(且访问权限够)
- 静态内部类可以访问外部类的变量和方法
- 如果是静态变量和方法可以直接访问。(且访问权限够)
- 如果是非静态变量和方法,需要实例化外部类然后才能访问。(且访问权限够)
- 当外部类被加载的时候,静态内部类不会被加载,只有当第一次调用到该静态内部类时,才会被加载。
- 静态内部类类虽然写在了外部类的内部,但就像一个其他的不在外部类内部的普通的类一样(也就是说静态内部类实例化不需要外部类的名称,并且在实例化的时候需要用new 构造方法)
使用静态内部类的原因:
- 用内部类是因为内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类。所以没有必要专门用一个Java文件存放这个类。
- 可以给静态内部类添加一些类变量才有的访问修饰符,如 public、private 以及 protected,达到如类变量一样的访问权限控制
- private 修饰静态内部类时,只有外部类可以访问静态内部类中的静态变量以及方法,同时只有外部类可以实例化该静态内部类进而访问其中的非静态变量以及方法