1.封装
1.1广义封装和狭义封装(java)
封装,单词Encapsulation。
广义的封装:将一块经常要使用的代码片段,定义到方法中,是封装。将多个方法和多个状态数据定义到类体中,也是一种封装。
1.2 java封装(狭义封装)
封装的原因:
一个类中的某一些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进行的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起来,不让外界直接访问。
封装的方法 :
- 为了不让外界直接访问某些属性,用关键字private修饰这些属性。
- 提供共有的getter/setter的方法,用来操作这个被私有化的属性。
为什么封装之后还要添加setter和getter方法?
可以通过指定的方式访问属性,在这些方法中,可以添加一些数据处理操作。
测试代码(主类部分):
public class APerson { // 成员变量私有化 private String name; private int age; private char gender; //无参构造器 public APerson(){} //全参构造器 public APerson(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; } //为每个成员变量,提供getter/setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age > 0 && age <= 120) { this.age = age; }else{ System.out.println("--您赋值的年龄不合法--"+age); } } public char getGender() { return gender; } public void setGender(char gender) { if(gender == '男' || gender == '女'){ this.gender = gender; }else{ // System.out.println("--您赋值的性别不合法,需要汉字'男'或'女'--"); //如果赋值有问题,可以使用下面的方式让程序中断,并进行提示 throw new RuntimeException("您赋值的性别不合法,需要汉字'男'或'女'--"); } } }
测试代码(测试部分测试类)
public class APersonTest { public static void main(String[] args) { //创建对象 APerson p1 = new APerson(); //p1.name = "小明"; name私有化了,就不可以直接访问 //调用公有方法区访问 p1.setName("小明"); p1.setAge(18); p1.setGender('女'); //System.out.println(p1.name); 也不能直接访问,需要调用公有的方法 System.out.println(p1.getName()); System.out.println(p1.getAge()); System.out.println(p1.getGender()); p1.setGender('m'); System.out.println(p1.getGender()); } }
注意。setter/getter方法在idea中有快捷生成方式,如下图所示。
1.
2.
点击后选中要生成方法的变量就完事了。
2.单例设计模式
2.1设计模式简介
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计的经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
总体来说设计模式分为三大类:
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
只学习单例模式:
单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。它的目的是确保一个类只有一个实例,并提供一个全局访问点。
使用场景:
频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。
控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。
工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。
2.2单例的饿汉模式
单例模式的饿汉模式
1. 提供一个该类的,私有的,静态的属性, 并在静态代码块里赋值
2. 提供一个私有的构造器, 避免外界直接调用构造器
3. 提供一个公有的,静态的方法,来获取当前类的对象
代码如下:
public class BSingleton { private static BSingleton instance; // instance 实例的含义 static{ instance = new BSingleton(); } //构造器私有化 private BSingleton(){} public static BSingleton getInstance(){ return instance; } }
2.3单例的懒汉模式
单例模式的懒汉模式
1.提供一个该类的,私有的,静态的属性。
2.提供一个私有的构造器,避免外界直接调用构造器。
3.提供一个共有的,静态的方法,来创建当前类的对象。如果对象不存在,说明对象还未创建。
4.懒汉模式,有线程安全隐患问题,比如两次调用的时候,都恰好执行到判断等于null。都会执行到创建实例那一行代码,那么就会在内存中创建出来两个对象。
代码如下:
public class CSingleton { //私有化的静态变量 private static CSingleton instance; //私有化构造器 private CSingleton() { } //公有的静态方法,用于获取对象 public static CSingleton getInstance() { //如果静态变量里没有地址,说明还未创建对象。 if(instance == null) { //创建对象,将地址存入静态变量 instance = new CSingleton(); } //返回对象的地址,之前有,就用之前的,没有,就用新的。 return instance; } }
2.4 两者的比较
基本都是一样的,都可以获取到一个类的唯一的对象。
1 、在没有使用获取当前类对象之前,懒汉式单例比饿汉式单例在内存上有较少的资源占用。
2 、懒汉式单例在多线程的环境下有问题。需要考虑线程安全(学到线程再说)
3. 继承
3.1继承的简介
继承是面向对象最显著的一个特征。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
已有的类,叫父类,又叫基类,超类。 派生出来的新类,叫子类,也叫派生类。
使用关键字extends 来表示子类继承了父类,语法如下:
修饰词 class 子类名 extends 父类名{
//子类的类体
}
例子:
class A{} //父类
class B extends A{} //B是A的子类
class C extends B{} //C是B的子类
3.2 继承的特点
- Java只支持单继承,即一个类只能有一个父类;但是一个类可以有多个子类。
- java支持多重继承,即一个类在继承自一个父类的同时,还可以被其他类继承。 可见继承具有传递性
- 子类继承了父类的所有成员变量和方法,包括私有的(只不过没有访问权限),当然也包括静态成员。
- 子类不会继承父类的构造方法,只能调用父类里的构造方法,并且一定至少有一个子类构造器调用了父类的构造器。
- 子类在拥有父类的成员的基础上,还可以添加新成员。
如下图所示:
3.3 继承中的构造器
一个对象在实例化的时候,需要在堆上开辟空间,堆中的空间分为两部分,分别是从父类继承到的属性,子类特有的属性。而实例化父类部分的时候,需要调用父类中的构造方法。
值得注意的是,默认调用的是父类中的无参构造器。如果父类中没有无参构造器,那么子类需要显式调用父类中的某一个有参构造器。
在子类的构造方法中,使用 super(有参传参) 调用父类中存在的构造方法,而且 super(有参传参) 必须放在首行首句的位置上。因此super(有参传参)和this(有参传参)不能在一个构造器中共存。
再重复一下:
构造器:
- 子类不能继承父类的构造器
- 子类的构造器中可以使用super(有参传参)进行显式的调用父类中的某一个构造器
- super(有参传参)和this(有参传参)一样,必须放在首行首句,因此不能并存。
- 子类的构造器中,至少存在一个构造器调用了父类的构造器。
- 为什么要至少有一个调用了父类的构造器:因为子类继承过来的属性需要初始化。
代码(APerson类——父类)
public class APerson { private String name; private int age; private char gender; public APerson() {} public APerson(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public char getGender() { return gender; } public void setGender(char gender) { this.gender = gender; } }
代码(Student——子类)
定义一个学生Student类型,将人的特征全部继承过来
学生类独有的特征:
学号: studentId
班级编号:classNumber
public class Student extends APerson{ private String studentId; private String classNumber; //添加自己的构造器 public Student(String studentId, String classNumber) { // super("小黑",10,'女'); this.studentId = studentId; this.classNumber = classNumber; } public Student(String name,int age,char gender,String studentId,String classNumber) { // this.name=name; 父类里的name属性私有化,虽然子类继承了,但是不能直接访问。 // setName方法是继承过来的,并且是公有的,因此可以直接使用 this(classNumber,studentId); setName(name); setAge(age); setGender(gender); // this.studentId = studentId; // this.classNumber = classNumber; } public String getStudentId() { return studentId; } public void setStudentId(String studentId) { this.studentId = studentId; } public String getClassNumber() { return classNumber; } public void setClassNumber(String classNumber) { this.classNumber = classNumber; } //添加toString方法:用来显示对象的属性值。 public String toString() { return "name: "+getName()+"\nage: "+getAge()+"\ngender: "+getGender()+"\nstudentId: "+studentId+"\nclassNumber: "+classNumber; } }
3.4 继承中的方法重写
重写,叫做override。在子类中,对从父类继承到的方法进行重新的实现。 这个过程中,子类重写该方法,会覆盖掉继承自父类中的实现方法,因此,重写又叫做覆写。
为什么要重写
因为父类的方法逻辑不能满足子类的需求了,因此子类需要修改逻辑,也就是重写。
重写的特点:
- 子类只能重写父类中存在的方法。(不存在的就是子类自己的方法)
- 重写时,子类中的方法名和参数要与父类保持一致。(区别于重载overload)
- 返回值类型:必须和父类方法的返回值类型相同,或者是其子类型。
- 访问权限:子类重写方法的访问权限必须大于等于父类方法的访问权限。
代码部分:
package com.oop.day02._02Extend; /* 演示:继承中的方法的重写特点。 */ public class TDog extends Animal{ public static void main(String[] args) { //创建了一个子类型对象 TDog dog = new TDog(); //调用继承过来的并且有访问权限的方法 dog.motion(); } //子类dog独有的功能 // @Override//注解只能放在子类重写父类的方法的上面,可以用于检测是不是重写,不是重写,就报错。 public void noise(){ System.out.println("--汪汪汪汪--"); } //子类将父类的方法,完全一模一样的写出来,就是重写的一种。 @Override public void motion(){ System.out.println("--运动中--"); } //子类在重写父类方法时,返回值类型与父类中的方法返回值可以相同,也可以是其子类型:TDog就是Animal的子类型。 @Override public TDog getMyclr(){ return null; } //子类在重写父类的方法时,访问权限应该大于等于父类方法的去访问权限。 @Override public String showInfo(){ return null; } //自己独有的方法,只不过与继承过来的那个是重载关系 public String showInfo(int a){ return null; } } class Animal{ private String color; //公有的,返回值void public void motion(){ System.out.println("--运动中--"); } //公有的 返回值Animal public Animal getMyclr(){ return null; } //默认的,返回值String String showInfo(){ return null; } }
关于注解@Override
这个注解,用在重写的方法之前,表示验证这个方法是否是一个重写的方法。如果是,程序没有问题。如果 不是,程序会报错。 因为我们在进行方法重写的时候,没有什么提示的,因此,在进行重写之前,最好加上这个注解。
误区:加了@Override就是重写,没有加@Override就不是重写。 这种说法是错误的! @Override只是进行的一个语法校验,与是不是重写无关。
面试题 : 简述 Override 和 Overload 的区别
Override: 是重写,是子类对父类的方法进行重新实现。
Overload: 是重载,是对同一个类中的同名、不同参数方法的描述。
3.5 Object类型
Object类,是Java中的根类。所有的类都直接或者间接的继承自Object类。因为,所有的类都直接或者间接的继承自Object类,因此在Object类中定义的属性、方法,在所有的类中都有包含。 比如常用的方法 hashCode(),equals(),toString(),getClass(),wait(),notify(),notifyAll()等。
1)toString()方法
源码如下:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
该方法的作用是将对象的信息变成字符串。 Object里返回的是 “类名@16进制” 。这个返回结果意义不大。看不到对象的成员变量的信息。因次一般都需要重写。
该方法。在使用输出语句打印对象的变量时,会自动调用。
重写:可以在idea里直接生成。
@Override public String toString() { return "Teacher{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}'; }
2) equals()方法
源码如下:
public boolean equals(Object obj){ return this==obj; // == 比较的是地址值。 }
上述的源码的意义所在:比较this和传进来的obj 是不是同一个对象,如果是,则返回true,不是的话,返回false。
而我们大多时候的需求,并不是比较是不是同一个对象,而是比较两个对象的属性值是否相同。因此:自定义类型时,应该重写eqauls方法。
重写要遵循一些原则:
- 如果 obj = null,一定要返回false。
- 如果 obj = this,一定要返回true。
- 如果两个对象的类型不同,一定要返回false。
- 如果 a.equals(b) 成立,则 b.equals(a) 也必须成立。
- 如果 a.equals(b), b.equals(c) 成立,则 a.equals(c) 也必须成立。
代码:
public class Teacher { private String name; private int age; private char gender; public Teacher(){} public Teacher(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; } //重写equals @Override public boolean equals(Object obj){ if(obj == null) return false; if(obj == this) return true; if(obj instanceof Teacher) { Teacher t = (Teacher) obj; return this.name.equals(t.name) && this.age == t.age&& this.gender == t.gender; } return false; }
重写equals方法
1.判断一下,传入的是不是null
2.判断一下,传入的是不是自己
3.判断一下,是不是同类型,如果是就转成同类型进行比较
4.其他任何情况,都返回false。
3)hashCode()方法
该方法返回的是一个int类型的值。 表示对象在内存堆中的一个算法值。 在自定义类型时,一般都需要重写该方法
4)getClass()方法
方法原型 : public final native Class<?> getClass();
方法作用 : 获取一个用来描述指定类型的Class类对象。获取一个指定的对象的类型。 这个方法,不能被重写。
//通过对象调用getClass(),来获取Teacher对应的描述类的副词昂 Class<? extends Teacher> aClass=t1.getClass(); System.out.println(aClass.getName()); //获取类里的所有属性 Field[] fields =aClass.getDeclaredFields(); for(Field f:fields){ System.out.println(f.getName()); }
3.6 final修饰词
final是java语法中的修饰词,可以用来修饰类,属性,方法,局部变量。 final是“最后,最终”的含义。
特点:
- 修饰类时,类不能再有子类,即不能被继承, 比如String, 八大基本数据类型的包装类
- 修饰属性:只能赋值一次,即要么直接初始化,要么在构造器初始化。
- 修饰方法:final修饰的方法,不能被重写。
- 修饰局部变量, 只能赋值一次,不能第二次赋值。
public class Cat { private final String name; public Cat(String name) { this.name = name; } public final int sum(int a,int b){ //测试final修饰局部变量 final int x; x = 100; // x = 101; return a+b; } //下面的方法:给name赋值已经不是初始化了,因为想要执行该方法,那么构造器一定先执行了。 // public void setName(String name) { // this.name=name; // } } //不能继承Cat,因为Cat是final修饰的 //class ACat extends Cat{} //探究一下父类里的方法被final修饰,还能不能参与重写 class DCat extends Cat{ public DCat(String name) { super(name); } //不能重写父类里的final方法,否则报错。 // public int sum(int a, int b){ // return a+b; // } }
上述代码注意注释部分的解释。
3.7 static修饰词
static修饰的内容,都是属于公有资源,不属于对象,而是属于类的,因此都是类名调用。
static 修饰词
1.修饰成员变量:
静态成员变量,属于类的,公共资源,使用类名.调用
注意:可以使用引用变量.调用,但是不合理,因为不属于对象。
2.修饰方法:
静态方法,属于类的,公共资源,使用类名.调用
注意:可以使用引用变量.调用,但是不合理,因为定义的方法不属于对象,属于大家。
静态方法中,不能直接访问非静态成员。
static方法不能被重写,但是子类里可以写跟父类里一模一样的静态方法,不是重写,各是各的
3.修饰代码块,
静态代码块,类加载时只执行一次。通常用于加载静态资源:图片,音频等4.修饰类:
可以修饰内部类。
代码解释:
public class MyUtil { private String name; //定义一个水桶的容量,单位是1L public static int contain = 18; //如果想要达到每时每刻看到的共有资源都是一样的,那么就应该再加上使用final修饰,即常量。 public static final double PI = 3.14159265358979323846; //final 和 static顺序无所谓。 public void addContain() { this.contain++; } public void subContain() { this.contain--; } public static void sum(int a, int b) { //不能直接访问非静态成员 // this.aaaa; // addContain(); contain = 19; } public static void main(String[] args) { MyUtil p1 = new MyUtil(); //查看容量 //使用变量调用,但是不建议。 System.out.println("contain = " + p1.contain); p1.addContain(); System.out.println("contain = " + MyUtil.contain); //注意:静态变量要使用类名.调用 MyUtil p2 = new MyUtil(); p2.subContain(); } } class sub extends MyUtil { // @Override 添加注解后报错,因为static方法,不能被重写,但是子类里可以写跟父类里一模一样的静态方法,不是重写,各是各的 public static void sum(int a, int b) { //不能直接访问非静态成员 // this.aaaa; // addContain(); contain = 19; } }
4.多态
多态:从字面上理解,就是多种形态,多种状态的含义,在这里,指的是一个对象具有多种形态的特点。说的再简单点,就是一个对象可以从一种类型转换为另外一种类型。有向上转型和向下转型两种形式
4.1向上转型(向上造型)
- 方法:父类型的变量引用子类型的对象。
- 向上转型肯定会成功,是一个隐式转换。
- 向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)
- 如果调用的是重写过的方法,那么调用的一定是重写过的方法(运行期间,看对象)
- 应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象
代码:(父类)
public class Animal { private String color; private String name; private int age; public Animal() {} public Animal(String color, String name, int age) { this.color = color; this.name = name; this.age = age; } //动物都会叫 public void noise(){ System.out.println("---------动物都会叫--------"); } } class Dog extends Animal{ public Dog(String color, String name, int age) { super(color, name, age); } public void noise(){ System.out.println("-------汪汪汪-----"); } public void LookHouse(){ System.out.println("--看家--"); } } class Cat extends Animal{ public Cat(String color, String name, int age) { super(color, name, age); } public void noise(){ System.out.println("----喵喵喵喵----"); } public void catchMouse(){ System.out.println("---抓老鼠---"); } }
向上转型的代码测试:
public class AnimalTest { public static void main(String[] args) { Animal a1 = new Cat("baide","下滑",3); Animal a2 = new Dog("sad","白",10); a1.noise();//编译期间不会出现问题,因为父类型里面有该方法。运行期间执行的是对象的类型里的方法逻辑 // a1.catchMouse();调用不到该方法,因为a1这个变量的类型里没有该方法,(编译期间看变量类型) test1(a1); test1(a2); } //测试:执行动物的叫声 这就是向上造型的优势所在,父类型的变量作为参数,更加灵活,可以传入不同的子类型对象。 public static void test1(Animal an){ an.noise(); } }
4.2 向下转型
父类型变量赋值给子类型的变量,需要强制转换,是一个显式转换。
多态的另外一种形式:向下转型。
父类型的变量赋值给子类型的变量,需要强制转换。
该操作可能会出现失败,失败的话,就会报异常;ClassCastException 类造型异常
如果想要避免失败,可以使用 instanceof 关键字来进行判断:
该变量指向的对象是否属于某一个类型。如果是,返回true,否则返回false为什么向下转型: 前提,父类型的变量引用子类型的对象,但是父类型的变量调用不了子类里独有的功能,已经不能满足需求。
应用场景:
多数情况下,定义方法时,形参都是父类型的变量,原因是可以接收任意子类型的对象
但是有时候,在这样的方法逻辑中,可能会用到该对象的独有功能,因此会涉及到向下转型。
向下转型代码:
public class AnimalTest2 { public static void main(String[] args) { Animal am = new Dog("yellow","大黄",5); am.noise(); //调用一下对象的独有功能 // am.LookHouse();//编译期间看变量类型,没有该方法,所以报错。 //只能向下转型才可以 Dog d =(Dog)am; d.LookHouse(); //上述代码没有问题,但是在真正编程是,有可能写成如下代码:强制转换的类型不正确 //编译期间,不报错。但是运行期间,就会报异常。 // Cat c =(Cat)am; // c.catchMouse(); //我们应该避免上述情况发生。只需要使用instanceof即可 if(am instanceof Cat){//因为am指向的是一个Dog对象,不属于Cat类型,因此进不去分支。 //所以避免了报错 Cat c = (Cat)am; c.catchMouse(); } } }