目录
类和对象
面向对象四大特征
封装
封装就是把信息进行隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过这些对外接口与这个封装的对象进行交互和调用。也就是说用户不需要知道被封装对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
一个对象它所封装的是自己的属性和方法
使用封装的好处:
- 良好的封装能够减少耦合
- 容易地修改类的内部实现,无需修改使用了该类的客户代码,可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节
public class Teacher {
//封装的属性
private String name;
private Integer age;
private String sex;
//外部可以调用的接口
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
继承
类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。
实际上继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。例如猫有抓老鼠等其他动物没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为“向上转型”。
对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
继承的特点:
- 子类拥有父类非private的属性和方法
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法(方法重写)
- 父类变,子类就必须变
- 继承破坏了封装,对于父类而言,它的实现细节对对子类来说都是透明的
- 继承是一种强耦合关系。
构造器
子类继承不了父类的构造器。对于构造器而言,它只能够被调用,而不能被继承。调用父类的构造方法我们使用super()。构建过程是从父类开始向子类一级一级地完成构建。
如果没有显示的引用父类的构造器,编译器会默认给子类调用父类的构造器,前提是:父类有默认构造器。如果父类没有默认构造器,我们就要必须显示的使用super()来调用父类构造器,而且必须是在子类构造器中做的第一件事(构造方法的第一行代码),否则编译器会报错。
向上转型
继承是is-a的相互关系,猫继承于动物,所以我们可以说猫是动物,或者说猫是动物的一种。这样将猫看做动物就是向上转型,即将子类的实例赋值给父类的引用。
public class Animal {
static void eat(){
System.out.println("吃饭");
}
}
public class Cat extends Animal{
public static void main(String[] args) {
Animal a = new cat();
}
}
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
指向子类的父类引用由于向上转型,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用这些方法的时候,必定是使用子类中定义的这些方法。
对于面向对象而言,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,通过静态绑定来实现,主要是指方法的重载,它的方法名相同,根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。
而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性,主要是指方法重写,子类重写父类的方法,它的方法名和参数列表是与父类相同的,方法的内容根据子类的需求进行调正。子类的权限大于等于父类的权限。父类中的静态方法、final修饰的方法以及private修饰的方法不能被重写。
实现条件
Java实现多态有三个必要条件:继承、重写、向上转型。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
实现形式
在Java中有两种形式可以实现多态:继承和实现接口。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。
抽象
普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分。
在所有的普通方法上都会有方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。
而拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
abstract class A{//定义一个抽象类
public void fun(){//普通方法
System.out.println("存在方法体的方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
抽象类的使用原则:
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public
- 抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理
- 抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类
- 子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。)
注意事项:
- 抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
- 抽象类不能用final声明,因为抽象类必须有子类,而final定义的类不能有子类
- 外部抽象类不允许使用static声明,而内部的抽象类允许使用static声明
final关键字
final表示最终的,不可变的。final可以修饰变量以及方法,还有类等。
- final修饰的类无法被继承
- final修饰的方法无法被覆盖或者重写
- final修饰的局部变量只能赋值一次
- final 修饰实例变量系统不负责赋默认值,要求必须手动赋 值,只能赋一次,这个手动赋值,在变量后面赋值可以,在构造方法中赋值也可以
- final修饰的实例变量一般添加static修饰 变成静态的,存储在方法区,节省空间。static final联合修饰的变量成为常量,常量名要全部大写,每个单词之间采用下划线衔接
public static final String NAME="Yan";
- final修饰引用只能指向一个对象
static关键字
static的作用
修饰变量
static修饰成员变量时,该成员变量的数据就是一个共享的数据。
静态成员变量的访问方式:
方式一: 使用对象进行访问。对象.属性名
方式二:可以使用类名进行访问。类名.属性名
注意:
非静态成员变量不能类名直接访问,只能使用对象进行访问。
千万不要为了方便访问成员变量而使用static修饰,一定要是该数据是共享数据时才使用static修饰。
静态变量和实例变量的区别:
(1)静态变量存储在方法区当中;实例变量存储在堆上
(2)静态变量和类有关,一个类只有一份静态变量;实例变量和对象有关,有几个对象就有几份实例变量
(3) 静态的成员变量数据是随着类的加载而存在,随着类文件的消失而消失;非静态的成员数据是随着对象的创建而存在,随着 对象被垃圾回收器回收而消失。
修饰方法
静态方法的访问方式:
方式一:可以使用对象进行访问。对象.静态的函数名();
方式二:可以使用类名进行访问。类名.静态函数名字()。
比较推荐使用类名直接访问静态的成员。
静态方法和实例方法的区别:
- 静态函数是可以调用类名或者对象进行调用的,而非静态函数只能使用对象进行调用。
- 静态的函数可以直接访问静态的成员,但是不能直接访问非静态的成员;非静态的函数是可以直接访问静态与非静态的成员
- 静态方法不能被重写
修饰类
static可以用来修饰内部类
静态内部类和普通内部类的区别:
1.静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
2.静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。
静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计。
static还用于修饰单例模式,类的静态成员变量就是指的类共享的对象,而单例模式的对象设成静态就是为了让该类所有成员共享同一个对象;
从语法考虑,常见的单例模式都是通过一个静态方法返回其单例,因为静态方法的内部不能直接使用非静态变量,所以返回的这个实例就是静态的。
单例模式
java中单例模式是一种常见的设计模式,单例模式的写法有好几种,常见的主要有两种:懒汉式单例和饿汉式单例
单例模式的特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
使用单例模式是为了保证在Java应用程序中,一个类Class只有一个实例存在。
饿汉单例模式
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以饿汉单例模式是线程安全的。
懒汉单例模式
懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
该懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
解决方法
1、在getInstance方法上加synchronized关键字实现线程同步
在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
2、双重检查锁定
在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
public static Singleton getInstance() {
//第一次检查确保是空对象,空对象的线程进入同步代码块;第二次空对象检查是为了确保只创建一个实例化对象
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
3、静态内部类
利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
访问限定符
Java的有四种访问权限:
public :公共权限
- 可以修饰类、构造方法、数据成员、方法成员
- 可以被同一个类里面的其他类访问
- 可以被任意包下的类访问
- 不管是同一包还是不同包下的子类也可以访问
private:私有权限
- 可以修饰构造方法,数据成员,方法成员,除外部类的其他类
- 可以被同一个类里面其他类访问
- 不能被任何其他的类访问
protected:受保护权限
- 可以修饰数据成员,构造方法,方法成员,除外部类的其他类
- 可以被同一个类里面的其他类访问
- 可以被同一个包下的类访问
- 不能被不同包的类访问
- 可以被子类访问
default:默认权限
- 可以修饰类,构造方法,数据成员,方法成员
- 可以被同一个类里面的类访问
- 可以被同一个包里面的类访问
- 但是不能被不同包下的类访问,不管是子类还是非子类
这四种访问限定修饰符的权限大小为:public>protected>default>private
重写和重载
重写
重写是指在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以对方法体进行修改或重写,这就是重写。
重写需要的的条件:
- 发生在父类与子类之间
- 方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
- 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
- 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
重载和重写的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。