一、单例设计模式
1、什么是单例设计模式?
对于单例模式(Singleton Pattern)是一个比较简单的模式,他的定义如下:
Ensure a class has only one instance,and provide a global point of access to it.
意思是确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
好处:保证对象的唯一性
使用场景:比如多个程序都要使用一个配置文件中的数据,而且要实现数据共享和交换。必须要将多个数据封装到一个对象中。而且多个程序操作的是同一个对象。那也就是说必须保证这个配置文件对象的唯一性。
如何能保证对象的唯一性?
一个类只要提供了构造方法,就可以产生多个对象。完全无法保证唯一。既然数量不可 控,干脆不让其他程序建立对象。
不让其他程序创建,对象何在?
自己在本类中创建一个对象,好处,对象可控。
创建完成后,是不是要给其他程序提供访问的方式?
怎么实现这个步骤?
怎么就能不让其他程序创建对象呢?
直接私有化构造方法,不让其他程序创建的对象存在。
直接在本类中new一个本类对象
定义一个功能,其他程序可以通过这个功能获取到本类对象。
2、单例模式分成两种:懒汉式、饿汉式
3、下面就通过代码来看看这三种单例模式的区别
饿汉式:
116//饿汉式 117class Singleton_1{ 118 private static Singleton_1 s = new Singleton_1(); 119 private Singleton_1(){} 120 public static Singleton_1 getInstance(){ 121 return s; 122 } 123 124}
懒汉式:(容易引发线程安全问题)
125//懒汉式(单例模式的延迟加载方式),面试最多的是懒汉式 126class Singleton_2{ 127 private static Singleton_2 s = null; 128 private Singleton_2(){} 129 public static Singleton_2 getInstance(){ 130 if(s == null){ 131 s = new Singleton_2(); 132 } 133 return s; 134 } 135}
二、继承
1、假设有两个类,一个类是Student类,另外一类是Worker类,两个类中都有属性name和age,那么既然有共同的属性,我们能不能对他们进行抽取呢?
答案:可以,可以将两个类中的name属性和age属性都抽取取来,放到另外一个类,这个类就是Person,因为学生是人,工人也是人,所以就抽取出来再建立Person类。
继承的关键字:extends
代码体现:
136package day08.itcast01; 137public class ExtendsDemo { 138 public static void main(String[] args) { 139 Student s = new Student(); 140 s.name = "林青霞"; 141 s.age = 16; 142 s.show(); 143 144 Worker w = new Worker(); 145 w.name = "小二"; 146 w.age = 28; 147 w.show(); 148 } 149} 150class Student extends Person{//定义一个Student类,并提供一个成员方法 151 public void study(){ 152 System.out.println("我在学习"); 153 } 154} 155class Worker extends Person{ //定义一个Worker类,并提供一个成员方法 156 public void work(){ 157 System.out.println("我在工作"); 158 } 159} 160class Person{ //定义一个Person类,由Student类和Worker类抽取而来,是Worker类和Student类的父类(基类、超类) 161 String name; 162 int age; 163 public void show(){ 164 System.out.println(name+"---"+age); 165 } 166}
继承的好处:提高了代码的复用性,为面向对象另一个特征多态提供了前提条件
什么时候定义继承?
必须保证类与类之间的所属(is a)关系,XX是XXX的一种
比如:狗是动物的一种,学生是人的一种
Java当中允许单继承,不允许多继承
单继承:一个子类只会有一个父类
多继承:一个子类会有多个父类
2、继承中子父类成员的特点:
1、成员变量
特殊情况:当子类和父类定义了同名的成员变量的时候,如何在子类中访问父类中的变量?
通过关键字super来完成
super的用法与this的用法类似,
this代表的是本类对象的引用。
super代表的是父类的内存空间
2、成员方法
当子类和父类定义了一模一样的的方法时,当子类调用该方法时,运行的是子类中的方法
这种情况在子父类中被称之为方法的重写。
何时需要重写方法?
当子类的方法有自己的特有的功能的时候就需要重写。
子类重写父类的方法必须保证权限要大于或者等于父类的权限
静态只能覆盖静态的
写法上需要注意的:必须一模一样,方法的返回值类型 方法名 参数列表都要一样。
代码体现:
167package day09.itcast01; 168public class ExtendsDemo1 { 169 public static void main(String[] args) { 170 Zi zi = new Zi(); 171 zi.age = 15; 172 zi.des(); 173 } 174} 175class Fu{ 176 int age; 177 public void des(){ 178 System.out.println("父类的des方法"+"---父类的成员变量---"+age); 179 } 180} 181class Zi extends Fu{ 182 int age; 183 public void des(){ 184 super.age = 10; 185 super.des(); 186 System.out.println("子类的des方法"+"---子类的成员变量---"+age); 187 } 188 }
3、子父类中构造方法的的特点
190package day09.itcast02; 191public class ExtendsDemo2 { 192 public static void main(String[] args) { 193 Son son = new Son(); 194 } 195} 196class Father{ 197 Father(){ 198 System.out.println("Father is running"); 199 } 200} 201class Son extends Father{ 202 Son(){ 203 System.out.println("Son is running"); 204 } }
分析运行结果:
因为在子类的所有构造方法中的第一行都默认有一个super();它会调用父类的构造方法
为什么会有super();呢?
因为在子类进行初始化的时候,先要对父类进行初始化,只有对父类进行初始化完成后,才能使用父类的一些方法
当父类没有空参的构造方法时,需要使用super关键字去调用相应的构造方法
如果在子类的第一行使用了this调用本类的其他构造方法,还会有super();吗?
没有,因为this()或者super()只能定义在构造方法的第一行
父类的构造方法中是否有super();
有,因为所有类的构造方法的第一行都有一个super();此时父类调用的是所有类的父类Object类。
如果默认的隐式super语句没有对应的构造函数,必须在构造函数中通过this或者super的形式明确调用的构造函数。
三、final关键字
继承的缺点:打破封装性,如何能保证继有继承又不会打破封装性呢?
就不让其他类继承该类,就不会重写方法。这时就需要用到final关键字
final的意思是最终,它用于修饰类,方法或者变量(成员变量、局部变量、静态变量)
final的特点:
1、final修饰的类是一个最终类,不能再派生子类
如果一个类中的方法部分需要重写,部分不需要,就对不需要被重写的方法使用final修饰
2、final修饰的方法是最终方法,该方法不能被重写
3、final修饰的变量是一个常量,只能被赋值一次
什么时候需要在程序中定义final常量呢?
当程序中一个数据使用时是固定不变的,这时为了增加阅读性,可以给该数据起个名字。
这就是变量,为了保证这个变量的值不被修改,加上final修饰,这就是一个阅读性很强的常量。
书写规范:被final修饰的常量名所有的字母都是大写,如果该变量名是由多个单词组成的,每个字母都大写,并且单词之间使用"_"连接。
四、抽象类
对与狗和狼这两种动物他们都有一个吼叫的行为,而且他们还属于动物,对他们的共性进行向上抽取,可以使用继承,但是狗和狼吼叫的行为又不同。这时使用继承就显得不合适了,这时就需要使用另外一个关键字abstract(抽象的)对父类进行修饰。
抽象类的特点:抽象类和抽象方法都需要使用abstract修饰,抽象方法一定要定义抽象类中,抽象类中的方法不一定都是抽象方法。
只有覆盖了抽象类中的所有抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。
抽象类要实例化的话需要通过子类对父类进行实例化。(多态)
细节:
抽象类一定是一个父类
是的,因为抽象类就是子类的功能不断抽取出来的
抽象类中是否有构造方法?
有,不能给自己的对象实例化,可以给子类的对象进行初始化。
抽象类和普通类的异同点?
相同:它们都是用来描述事物的,它们之间都可以定义属性和行为
不同:一般类可以具体的描述事物,抽象类描述的事物信息不具体
抽象类可以多定义一个成员:抽象方法。
一般类可以创建对象,而抽象类不能创建对象。
抽象类中是否可以定义普通方法?
可以,如果抽象类中定义了普通方法,那么其抽象类的作用就是不能对该类进行实例化。
抽象关键字abstract不能与哪些关键字共存?
final
private
static
代码体现:
206package day09; 207public class AbstractDemo { 208 public static void main(String[] args) { 209 Dog d = new Dog(); 210 d.show(); 211 d.speak(); 212 213 } 214} 215abstract class Animal{ 216 public void show(){ 217 System.out.println("Animal"); 218 } 219 public abstract void speak(); 220} 221class Dog extends Animal{ 222 public void speak(){ 223 System.out.println("小狗叫"); 224 } 225}
案例:
需求:公司中程序员有姓名,工号,薪水,工作内容。
项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
分析:程序员和项目经历都属于公司里的员工,而且他们都有共性:姓名、工号、薪水以及工作内容
226package day09.itcast02; 227public class AbstractDemo { 228 public static void main(String[] args) { 229 Programer p = new Programer("张三","448",5000); 230 Manager m = new Manager("李四","500",5000,2500); 231 p.content(); 232 p.show(); 233 m.content(); 234 m.show(); 235 236 } 237} 238abstract class Employee{ 239 String name; 240 String number; 241 int salary; 242 public Employee(String name,String number,int salary){ 243 this.name = name; 244 this.number = number; 245 this.salary = salary; 246 } 247 public abstract void content(); 248 public abstract void show(); 249 250} 251class Programer extends Employee{ 252 public Programer(String name,String number,int salary){ 253 super(name,number,salary); 254 } 255 public void content(){ 256 System.out.println("敲代码"); 257 } 258 public void show(){ 259 System.out.println(name+"---"+number+"---"+salary); 260 } 261} 262class Manager extends Employee{ 263 int pay;//奖金 264 public Manager(String name,String number,int salary,int pay){ 265 super(name,number,salary); 266 this.pay = pay; 267 } 268 public void content(){ 269 System.out.println("写规划"); 270 } 271 public void show(){ 272 System.out.println(name+"---"+number+"---"+salary+"---"+pay); 273 } 274 275}
五、接口
当一个抽象类中的方法都是抽象方法的时候,这个抽象类就有另外一个表现方式,叫做接口(interface)
定义接口使用关键字interface,格式:interface 接口名{}
接口中的成员已经被限定为固定的几种。
接口中的定义格式(两种)
1、定义变量,但是变量必须有固定的修饰,public static final,所以接口中的变量也被称之为常量。
2、定义方法,方法也有固定的修饰符,public abstract
接口中的成员都是公共的。
特点:
接口不可以创建对象
子类必须覆盖掉接口中的所有抽象方法后,子类才可以被实例化。否则子类是一个抽象类。
定义接口的子类,类与类之间的关系是继承,而子类与接口之间的关系是实现(implements)。
格式:
interface Animal{ // 定义一个动物接口
}
class Dog implements Animal{ //定义犬类实现了动物的接口
}
接口解决了多继承中调用不明确的弊端,讲多继承机制在java中通过多实现完成了
接口的出现避免了单继承的局限性
父类中定义事物的基本功能。
接口中定义事物的扩张功能。
类与类之间是继承(is a)关系,类与接口之间是实现(like a)关系.
接口与接口之间是继承关系,而且可以多继承。
抽象类和接口的区别:
接口中定义的方法都是抽象方法,不允许定义普通方法。
抽象类中可以允许有普通方法,但是抽象方法一定定义在抽象类中。
接口的思想:
1、对功能实现了扩展。
2、定义了规则。
3、降低了耦合性。
三、多态
什么是多态?
举例子:学生和工人,但是他们都是人中的某一类,这就是多态。多态就是一种事物的不同体现。
多态的体现:
父类的引用或者接口的引用指向了自己的子类对象。
好处:提高了程序的扩展性。
弊端:通过父类的引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法
多态的前提:继承或者实现。
多态通常都有重写操作。
代码体现:
276package day10.itcast01; 277public class DuotaiDemo1 { 278 public static void main(String[] args) { 279 Person p = new Student(); 280 p.eat(p); 281 Person p1 = new Worker(); 282 p1.eat(p1); 283 284 } 285} 286abstract class Person{ 287 public void eat(Person p){ 288 if(p instanceof Student ){ 289 System.out.println("吃饭"); 290 p.method(); 291 }else if( p instanceof Worker){ 292 System.out.println("吃饭"); 293 p.method(); 294 } 295 } 296 public abstract void method(); 297} 298class Student extends Person{ 299 public void method(){ 300 System.out.println("学习"); 301 } 302} 303class Worker extends Person{ 304 public void method(){ 305 System.out.println("工作"); 306 } 307}
2、多态调用子类的特有方法
Person p = new Studnet();
或者Person p = new Worker();
父类引用指向了子类的对象,这是让子类对象进行类型的提升(向上转型)
好处:提高了扩展性,隐藏了子类,弊端:不能使用子类的特有方法。
如果想要使用子类的特有方法,只有子类的对象才能使用,这时就需要使用向下转型。
向下转型属于强制类型转换,向上转型是自动类型转换。
308package day10.itcast01; 309public class DuotaiDemo1 { 310 public static void main(String[] args) { 311 Person p = new Student(); 312 p.eat(p); 313// p.write(); 报错 314 ((Student)p).writer(); 315 Person p1 = new Worker(); 316 p1.eat(p1); 317 ((Worker)p1).work(); 318 319 } 320} 321abstract class Person{ 322 public void eat(Person p){ 323 if(p instanceof Student ){ 324 System.out.println("吃饭"); 325 p.method(); 326 }else if( p instanceof Worker){ 327 System.out.println("吃饭"); 328 p.method(); 329 } 330 } 331 public abstract void method(); 332} 333class Student extends Person{ 334 public void method(){ 335 System.out.println("学习"); 336 } 337 public void writer(){ 338 System.out.println("学生的特有方法:写作业"); 339 } 340} 341class Worker extends Person{ 342 public void method(){ 343 System.out.println("工作"); 344 } 345 public void work(){ 346 System.out.println("工人的特有方法:干活"); 347 } }
注意:无论是向上转型还是向下转型,最终都是子类对象做着类型的变化。
【向下转型的注意事项】:
Person p = new Studnet();
Worker w = (Worker)p;
以上代码是不允许出现的,会导致ClassCastException异常
为了避免出现类转换异常,在进行类转换时常使用instanceof关键字对对象进行类型判断
格式:对象名 instanceof 类名
3、子父类成员的调用问题
1、成员变量:当子父类中出现了同名的成员变量时
编译时期:参考的是引用型变量所属的类是否有被调用的成员变量,没有,编译失败。
运行时期:参考的是引用型变量所属类中的成员变量。
口诀:编译运行看左边。
2、成员方法:当子父类中出现了同名的成员方法时
编译时期:参考左边,如果没有,编译失败
运行时期:参考右边
口诀:编译看左边,运行看右边。
对于成员方法是动态绑定到对象上。
3、静态方法
编译和运行都参考左边
静态方法是静态绑定到类上。
4、题目
看一下代码分析打印结果
349package day10.itcast02; 350public class DuoTaiTest3 351{ 352 public static void main(String[] args) 353 { 354 Fu f = new Zi(); 355 System.out.println("main :" +f.getNum()); 356 } 357} 358class Fu 359{ 360 int x = 4; 361 Fu() 362 { 363 System.out.println("A fu() : "+getNum()); 364 } 365 int getNum() 366 { 367 System.out.println("B fu getnum run...."+x); 368 return 100; 369 } 370} 371class Zi extends Fu 372{ 373 int x = 5; 374 Zi() 375 { //super();默认存在并调用父类的无参构造 376 System.out.println("C zi() : "+getNum()); 377 } 378 int getNum() 379 { 380 System.out.println("D zi getnum run...."+x); 381 return 200; 382 } 383}
结果分析:
分析这道题首先从main方法开始,Fu f = new Zi(); 程序首先会调用子类的构造方法,进入到子类的构造方法时,因为在子类的构造方法中第一行都会有默认的super();调用父类的构造方法,所以程序进入到 父类的构造方法之中,在父类的构造方法中,有一条输出语句,输出语句中调用了getNum()方法,那么这时候调用的是哪个呢?因为在main方法中,使 用了多态,使父类的对象引用指向了子类,所以调用的是子类的getNum方法,在getNum方法中输出了一个x,因为从程序开始一直到现在都还未对x进 行显示的初始化,所以x的值为0,所以最想打印的是D zi getnum run....0,接着getNum方法返回了200到父类的构造方法中,所以接着打印了A fu() : 200,当打印了两条语句之后,又回到了子类的构造方法中,这时子类的构造方法中同样有一条输出语句,在语句中调用了getNum()方法,这个 getNum方法同样是子类中的方法,因为此时对子类中的x已经完成了显式初始化,所以打印了D zi getnum run....5,打印完成后又回到了子类的构造方法中执行了输出语句,此时打印了C zi() : 200,至此Fu f = new Zi()完成了所有的操作,最后mian方法中的输出语句中同样调用的是子类中的getNum()方法,所以打印的是main :200
综合以上的分析,可以得出最后的结果为:
D zi getnum run....0
A fu() : 200
D zi getnum run....5
C zi() : 200
main :200
六、object类
object是所有的类的父类或者间接父类
在object类中有两个常用的方法
equlas和toString方法
equals:通常用来比较两象是个对否相等。
toString:返回对象的字符串表现形式,Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
一般来说equals和toString方法都需要被重写
案例:
384package cn.test; 385public class EqualsDemo { 386 public static void main(String[] args) { 387 Student s1 = new Student("谢群",18); 388 Student s2 = new Student("谢群",22); 389 boolean result = s1.equals(s2); 390 System.out.println("result:"+result); 391 System.out.println(s2.toString()); 392 s2.setAge(18); 393 result = s1.equals(s2); 394 System.out.println("result:"+result); 395 System.out.println(s2.toString()); 396 397 } 398} 399class Student{ 400 private String name; 401 private int age; 402 public Student(String name, int age){ 403 this.name = name; 404 this.age = age; 405 } 406 public Student(){} 407 public void setName(String name){ 408 this.name = name; 409 } 410 public String getName(){ 411 return name; 412 } 413 public void setAge(int age){ 414 this.age = age; 415 } 416 public int getAge(){ 417 return age; 418 } 419 public boolean equals(Object s){ 420 if(s == null){ 421 return false; 422 } 423 if(!(s instanceof Student)){ 424 return false; 425 } 426 if(s == this){ 427 return true; 428 } 429 Student s1 = (Student)s; 430 return this.name.equals(s1.name) && this.age == s1.age; 431 } 432 public String toString(){ 433 return this.name +"----"+this.age; 434 } 435 436 437}