1.封装
1.1广义封装和狭义封装(java)
封装,单词Encapsulation。
广义的封装:将一块经常要使用的代码片段,定义到方法 中,是封装 。将多个方法和多个状态数据定义到类 体中,也是 一种封装。
1.2 java封装(狭义封装)
封装的原因:
一个类中的某一些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进行的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起来,不让外界直接访问。
封装的方法 :
- 为了不让外界直接访问某些属性,用关键字private修饰这些属性。
- 提供共有的getter/setter的方法,用来操作这个被私有化的属性。
为什么封装之后还要添加setter和getter方法?
可以通过指定的方式访问属性,在这些方法中,可以添加一些数据处理操作。
测试代码(主类部分):
java
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("您赋值的性别不合法,需要汉字'男'或'女'--");
}
}
}
测试代码(测试部分测试类)
java
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中有快捷生成方式,如下图所示。
点击后选中要生成方法的变量就完事了。
2.单例设计模式
2.1设计模式简介
设计模式 (Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计的经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
总体来说设计模式分为三大类:
-
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
只学习单例模式:
单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。它的目的 是确保一个类只有一个实例,并提供一个全局访问点。
使用场景:
-
频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。
-
控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。
-
工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。
2.2单例的饿汉模式
单例模式的饿汉模式
提供一个该类的,私有的,静态的 属性, 并在静态代码块里赋值
提供一个私有的构造器 , 避免外界直接调用构造器
提供一个公有的,静态的方法,来获取当前类的对象
代码如下:
java
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。都会执行到创建实例那一行代码,那么就会在内存中创建出来两个对象。
代码如下:
java
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类------父类)
java
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
java
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)
- 返回值类型:必须和父类方法的返回值类型相同 ,或者是其子类型。
- 访问权限:子类重写方法的访问权限必须大于等于父类方法的访问权限。
代码部分:
java
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()方法
源码如下:
java
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
该方法的作用是将对象的信息变成字符串。 Object里返回的是 "类名@16进制" 。这个返回结果意义不大。看不到对象的成员变量的信息。因次一般都需要重写。
该方法。在使用输出语句打印对象的变量时,会自动调用。
重写:可以在idea里直接生成。
java
@Override
public String toString() {
return "Teacher{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}';
}
- equals()方法
源码如下:
java
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) 也必须成立。
代码:
java
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类对象。获取一个指定的对象的类型。 这个方法,不能被重写。
java
//通过对象调用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修饰的方法,不能被重写。
- 修饰局部变量 , 只能赋值一次,不能第二次赋值。
java
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.修饰类:
可以修饰内部类。
代码解释:
java
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向上转型(向上造型)
- 方法:父类型的变量引用子类型的对象。
- 向上转型肯定会成功 ,是一个隐式转换。
- 向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)
- 如果调用的是重写过的方法,那么调用的一定是重写过的方法(运行期间,看对象)
- 应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象
代码:(父类)
java
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("---抓老鼠---");
}
}
向上转型的代码测试:
java
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
为什么向下转型: 前提,父类型的变量引用子类型的对象,但是父类型的变量调用不了子类里独有的功能,已经不能满足需求。
应用场景:
多数情况下,定义方法时,形参都是父类型的变量,原因是可以接收任意子类型的对象
但是有时候,在这样的方法逻辑中,可能会用到该对象的独有功能,因此会涉及到向下转型。
向下转型代码:
java
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();
}
}
}