初学者如果不理解面向对象的概念可以看看我的另一篇博客:https://www.cnblogs.com/yangyuanhu/p/11287038.html
对象的内存
变量定义在栈中,而具体的对象数据则在堆区
构造方法
构造函数,构造器
语法要求
1.函数名称与类名一致
2.不允许有返回值类型
3.不允许使用return语句返回数据
特点:
new 对象时会自动执行
1.可以重载多个构造方法
2.当我们没有定义任何构造方法时,会自动生成无参的空构造方法
3.一旦我们定义类构造方法,系统将不会自动生成
构造方法只能通过两种方式来调用:
1.在构造方法中可通过this去调用其他的构造方法
2.通过new 关键字
//在普通方法中也不能去掉用构造方法
public class Constructer {
public Constructer(){
}
public Constructer(String name){
this(); //调用空参数构造方法
}
}
注意注意:调用其他构造方法只能位于方法的第一行语句;
方法参数查找采取就近原则
当对象创建时对象的所有属性都会被自动初始化
================================
封装
特点:
1.只能通过规定的接口(方法)访问内部数据
2.隐藏了内部实现细节,为了隔离复杂度,和提高安全性,提高了类内部的维护性
使用:
修改方法或属性的权限为private 私有化
为私有的属性提供对外开放(public)的setter 和getter方法;
public class Test {
private String name;//该属性被私有化 此时该属性尽在该类内部可以访问
public Test(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
包
概念:
包的本质是一个文件夹
当一个项目中出现大量的类是,维护性会降低,我们可以采取分包的方式来管理 ,将相关的类放到同一个包中,方便管理维护代码,
命名规范:
采用域名倒叙,+模块名称+功能名称
全小写
注意:不同包中可以存在相同名称的类
import
import用于导入某个类
语法:
1.导入某个包下的某个类 import com.yh.package1.Cat;
2.导入某个包下的所有类 import com.yh.package2.*;
注意:
1.当上述两条语句出现了相同的类名称时(package2中也有Cat),优先使用的是语义更清晰的即import com.yh.package1.Cat
2.第二种语法,仅能导入包下的类,不能导入包下的子包
我们也可以在代码中使用完全限定类名来避免冲突:
com.yh.package1.Cat c1 = new com.yh.package1.Cat();
c1.show();
static
static修饰的成员称之为静态成员
特点:
1.可以使用类名直接调用(当然对象也可以)
2.并且static修饰的成员属于类名称空间中,也就是说所有该类对象,共享该资源,相反的如果非static修饰的成员是每个对象独有的,例如每个人的name属性不同;
3.静态成员的生命周期跟随的类,类加载时被加载,虚拟机结束时被销毁;
4.在静态方法中不可以使用this关键字,因为static修饰的成员比对象先被加载
5.静态方法中只能访问静态成员
代码块
使用一对大括号产生一个代码块,当然了方法也是代码块
1.构造代码块
我们还可以在类中直接使用{}来定义出一个构造代码快 像下面这样
class Person(){
{
//构造代码块,会在创建类的时候先于构造函数执行,没创建一个对象就执行一次
}
}
2.普通代码块
class Person{
void show(){
//方法体
{
// 普通代码块
}
//方法体
}
}
3.静态代码块
在类中使用static来修饰的代码块就是静态代码块,其执行时机是在类被加载时,执行一次,后续不在执行,
什么时候使用?
当我们需要在使用类之前做一些初始化操作时
问题?
1.代码块是否是局部作用域 ? 是的每个代码块都是局部的
2.静态代码块中是否可以存在方法? 不可以局部代码块创建方法没有意义
代码块的执行顺序
静态代码块 -> 构造代码块 -> 构造方法
===============================================================
继承
一个类和另一个类之间的关系,是一种什么是什么的关系(A is aB)
如猪是动物,
好处:子类继承父类可以直接使用父类开放(非private)的已存在成员
使用
extends关键字建立继承关系,一个类只能有一个父类
class Animal{
String name;
}
class Person extends Animal{
public void show(){
System.out.println(this.name);//直接访问父类成员
}
}
重写/覆盖
override
什么是覆盖:
当子类出现了与父类完全一致(相同返回值类型,相同名称,相同参数列表)的方法时将产生覆盖
何时使用覆盖:
当父类的方法 无法直接满足需求时子类可以覆盖父类已有方法
注意:
1.子类覆盖时方法的返回值类型必须与原始方法返回值类型一致,或者是原始方法返回值类型的子类
2.子类覆盖方法时要求方法的权限必须大于等于原始方法的权限
3.父类的静态成员不会被覆盖,当子类定义了与父类完全一致的方法时,这个方法实际是属于子类的与父类没有关系
4.当子类出现方法名称与参数列表与父类方法一致时则认为是覆盖,要求返回值类型必须一致或是其子类
也就是说如果子类想定义新的方法要么名称不同,要么参数列表不同
补充:子类也可以定义与父类相同的属性,将覆盖父类相同的属性
权限修饰符
修饰符 | 本类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | yes | yes | yes | yes |
protected | yes | yes | yes | no |
default | yes | yes | no | no |
private | yes | no | no | no |
继承注意事项:构造方法无法被继承
super
当子类覆盖了父类的方法时,按照顺序将优先执行子类中的方法,当我们需要执行父类方法时
可以在子类中使用super关键字来调用父类的方法
class A{
public A(){
}
public show(){
System.out.println("hello java!");
}
}
class B extends B{
public B(){
super().show();
}
}
注意:在子类的构造方法中系统会默认添加super(); 如果父类不存在空参构造函数将编译错误,我们也可以手动的使用super来调用其他的构造函数;
强调:super()必须放在第一行
对象的创建过程
1.加载类(仅发生一次)
2.加载父类静态资源
3.加载子类静态资源
4.执行父类构造代码块
5.执行父类构造函数
6.执行子类构造代码块
7.执行子类构造函数
8.完成
this与super
this:
本类的,属性,方法,构造器
super:
父类的,属性,方法,构造器
注意:this和super不能同时出现在构造函数中
final
翻译为最终,可以修饰类,变量,方法
1.在继承关系中,被final修饰的方法无法被覆盖
2.final修饰变量时,就变成了常量,一旦初始化值不允许修改
3.final修饰类时,表示该类不能被继承
注解
JDK1.5 推出
用于对方法或变量进行说明
源码注解尽在编译完成时自动去除,用于对元素进行标记,方便编译器识别,例如@override
编译时注解指的是编译成class后依然存在的注解
运行时注解指的是,会对程序逻辑产生影响的注解,例如autowrite,transaction等..
=============================================
单例模式
常见23中设计模式,及其分类;
什么是单例:
某个类有且仅有一个实例,那ta就是单例类
java中的实现方式:
1.将构造函数私有化 以禁止外界自己初始化
2.利用static仅加载一次的特性,创建一个静态的对象作为类的私有属性
3.提供访问这个静态对象的方法
按照创建对象时机不同可以分为两种
1.饿汉式: 直接在声明static属性时创建对象 这种方法是线程安全
class SingleInstance{
private static SingleInstance obj = new SingleInstance();
private SingleInstance(){
//私有化的构造函数
}
public SingleInstance getInstance(){
return obj;
}
}
2.懒汉式:在获取对象是如果发现对象为空才创建 这种方法是非线程安全
public class Emperor {
//定义私有构造方法
private Emperor(){
System.out.println("create instance!");
}
//定义私有静态类对象
private static Emperor obj = null;
//定义公有静态方法返回类内的私有静态对象
public static Emperor get_instance(){
if(obj == null){
obj = new Emperor();
}
return obj;
}
}
优缺点:
优点:
节省空间,提高性能
缺点:
扩展困难
当对象长期不使用,困难会被回收导致异常
使用场景:
当程序需要共享同一份对象的数据时
当每个对象数据都相同时,则没有必要创建对个对象
当需要保证某些数据的一致性时,例如: 要生成唯一的id,如果每个对象都有一份自己的生成方式则可能造成数据错误
================================
多态
定义:
多个不同对象可以响应同一个方法产生各自不同的行为
分类:
1.编译时多态
指的是在编译阶段就能识别具体要执行的方法是哪一个,通常是方法重载,通过参数不同决定调用哪个方法
2.运行时多态
只有在运行时才能确定到底调用哪个方法
注意下面的内容都是针对运行时多态,是任何OOP语言的共同特性
特点:
出现多态的两个必要条件
1.必须具备继承关系
2.父类指针指向子类对象
ps:在python中没有这两个限制,python是动态类型,编译期间不会检查对象具体类型,更加灵活,但是也增加了风险
向上转型
指的是用父类指针引用子类实例,会自动将子类转换为父类,转换后将隐藏子类特有的成员,只能使用父类中定义的方法和属性,
向下转型
当明确某个对象就是某个子类对象时可以强制转换为子类,如此就可以重新访问子类中独有的成员
当不能明确对象是否是某个类型时可以使用 instanceof
if(obj instanceof Objcet){
System.out.println("obj is ObjectClass instance");
}else{
System.out.println("obj not ObjectClass instance");
}
注意:由于静态方法不能被重写,当子类和父类存在完全相同的静态方法时,向上转型后对象默认调用的是父类中的静态方法,也就是说不存在多态,调用的方法是明确的,跟随类型的
abstract 抽象类
什么是抽象类:
抽象类指的是类中包含抽象方法的类,
抽象方法指的是没有任何方法体的方法
使用abstract关键字类表示抽象方法 或抽象类
什么时候使用抽象类:
当父类需要规定子类应该具备某些方法,但父类本身不清楚方法具体如何实现时使用抽象类,
用于提前告诉子类,你应该具备哪些方法
特点:
存在抽象方法的类无法直接实例化产生对象,必须由子类继承并实现所有抽象方法才能实例化子类
如子类没有实现所有抽象方法,那么子类也只能作为抽象类
实例:
abstract class Person{
abstract public void show();
}
class Student extends Person{
@Override
public void show(){
System.out.println("show");
}
}
注意:
1.如果设计子类的人清楚的知道自己应该做的事情时(方法),可以不用抽象类,抽象类本质就是限制子类必须怎么怎么滴
2.需要注意final 不能与 abstract 同时出现
interface 接口
接口是一组功能(方法)的定义
接口的作用:
定义一组协议方法,让子类遵循协议中的要求去实现具体的操作,对于使用者而言,只需要掌握接口中定义的方法的使用即可,无需关心,具体是哪一个类实现的,更不用关心是如何实现的, 这将类的设计者和类的使用者之间的耦合度大大的降低了,
定义语法:
interface Name {
}
特点:
接口本质是一个类,但是与普通类有着众多区别
1.接口中的方法默认都是抽象的不能有方法体
2.接口中定义的变量默认都是静态常量
3.接口中的成员必然是public修饰的,即时没有明确声明
4.接口无法直接实例化
5.接口可以继承一个或多个其他接口
6,一个类可以同时实现多个接口
JDK1.8之后
某些情况下,接口中声明了很多抽象方法,然而子类不需要实现全部,而是想仅实现部分需要的,这种情况在1.8之前是不允许的,除非把这个子类变成抽象的,这不够灵活;
- 1.8之后,增加了新的方法修饰符,default,可以在接口中直接编写方法体,被default修饰的方法,子类可以选择性的重写
- 新增特性,可以为方法加上static修饰符
新特性的测试
案例:
package com.yh.src;
interface HDMI {
static int a = 0;
// 上述代码等价于public static final int a = 0;
//抽象方法
void generalMethod();
//上述代码等价于public void test();
//默认方法 子类可选重写
default void defaultMethod(){
System.out.println("默认的方法体");
}
//静态方法 子类不可重写
static void staticMethod(){
System.out.println("静态方法体");
}
}
class AA implements HDMI {
@Override
public void generalMethod() {
System.out.println("AA Implements generalMethod!");
HDMI.staticMethod();//静态方法只能有接口名称调用
}
@Override
public void defaultMethod() {
//super.test1();//无法通过super调用接口中的默认方法
HDMI.super.defaultMethod();//需要使用接口名称.super来调用
}
}
public class InterfaceRunner {
public static void main(String[] args) {
AA a = new AA();
a.generalMethod();
a.defaultMethod();//调用未被重写的默认方法
//a.staticMethod();//无法直接调用接口中的静态方法
HDMI.staticMethod();//静态方法只能由接口名称调用
}
}
default方法总结:
- default方法子类可以选择是否重写,调用顺序与普通继承相同,优先调用对象重写的方法,
- 当需要在重写方法中调用接口中的默认实现时使用
接口名称.super.方法名称
如HDMI.super.defaultMethod();
static方法总结:
- static方法无法被重写,也不能使用super调用,无论在什么位置只能使用接口名称来调用
PS:其实取消抽象方法和接口一样可以实现设计-使用松耦合,直接使用普通类作为父类,子类自己实现该实现的方法,当然了java为什么占领企业开发也正是因为其严谨,标准规范,
你会发现,为了提高灵活性接口好像变得和抽象类非常相似,.......干脆像python一样别整这么多约束,,,
接口多实现
与普通类不同的是一个类可以同时实现多个接口,增加了子类的扩展性
但是也带来了访问不确定性,
-
情况1:多个接口存在相同的方法(名称与参数列表相同)
interface IA{
default void test(){
System.out.println("IA test!");
}
}
interface IB{
default void test(){
System.out.println("IB test!");
}
}
//实现类直接编译失败,
class IMA implements IA,IB{
}
解决方案:
//在子类中重写冲突的方法,即可 当需要调用默认方法时,使用`接口名称.super.xx`来调用
class IMA implements IA,IB{
@Override
public void test() {
IA.super.test();
IB.super.test();
}
}
-
情况2:父类中存在相同的方法
interface IA{
default void test(){
System.out.println("IA test!");
}
}
interface IB{
default void test(){
System.out.println("IB test!");
}
}
class P{
public void test(){
System.out.println("IB test!");
}
}
class IMA extends P implements IA,IB{
}
这种情况下不会出现问题,将直接调用父类中的方法
-
情况3:多个接口中出现了重复的常量
interface IA{
int a =10;
int b =100;
}
interface IB{
int a =20;
}
class P{
int a = 20000;
}
class IMA extends P implements IA,IB{
public void func(){
System.out.println(a);//编译错误
System.out.println(IA.a);//使用接口名称来明确
System.out.println(super.a);//使super来指定访问父类
System.out.println(b);//不存在重复的时直接可以访问
}
}
此时子类无法明确选择一个要访问的常量,必须使用接口名称来明确
要访问父类时使用super来明确
补充:
子类对象可以直接访问接口中没有冲突的静态变量,但是无法直接访问静态方法需要使用接口名称调用
===================================
内部类
什么是内部类:
当一个类定义在一个类的范围中,则这个类称之为内部类,与之对应的是包含这个内部类的外部类
普通内部类(较少用)
实例:
class A{//外部类
class B{ //内部类
}
}
实例化内部类
public class InnerClass {
public static void main(String[] args) {
//实例化内部类方法1
A.B obj; //定义
obj = new A().new B();
//实例化内部类方法2
A aobj = new A();
obj = aobj.getInner();
//实例化内部类方法3
obj = aobj.new B();
}
}
class A{
public B getInner() {
return new B();
}
class B{
}
}
内部类访问外部类的成员
public class InnerClass {
public static void main(String[] args) {
//实例化内部类方法1
A.B obj; //定义
obj = new A().new B();//实例化
obj.test();//调用方法访问外部类的成员
}
}
class A{
int inta = 100;
int intb = 200;
public B getInner() {
return new B();
}
class B{
int intb = 300;
public void test(){
System.out.println(inta);//100 可以直接访问外部类成员
System.out.println(intb);//300 冲突时优先访问内部
System.out.println(A.this.intb);//200 冲突时指定访问外部
}
}
}
外部类访问内部类成员
class A{
int inta = 100;
int intb = 200;
public void accessInner() {
B b = new B();//实例化
b.test();//访问
}
class B{
int intb = 300;
public void test(){
System.out.println(inta);
System.out.println(intb);
System.out.println(A.this.intb);
}
}
}
外部类访问内部类成员时需要先实例化内部类的对象,通过对象访问即可
注意:普通内部类中不能包含任何static修饰的成员
静态内部类 (不常用)
可以将内部类使用static修饰,此时他就是静态内部类
特点:
-
与普通内部类的不同之处,与static修饰的其他成员有着相同的特征:
-
在类被加载时就一起加载,所以,可以直接实例化,不需要先实例化外部类;
-
同样的在静态内部类中不可以直接访问非静态修饰的外部类成员,也不可以访问外部类的this;
-
静态内部类中的可以包含静态成员并且也可以直接调用(
外部类.内部类.属性
);普通类中不可以有静态成员;
实例:
public class InnerClass2 {
public static void main(String[] args) {
System.out.println(OUT.IN.a);//直接访问静态内部类的静态成员
OUT.IN.test();//访问静态内部类方法
}
}
class OUT{
static int a = 10;
String name = "jack";
static class IN{
static int a = 100;
static public void test(){
System.out.println(a);//优先访问内部类
System.out.println(OUT.a);//指定访问外部类
System.out.println(new OUT().name);//访问外部非静态成员
}
}
}
局部内部类 (不常用)
定义在方法中的类称为局部内部类
意义不大..没啥用..略过了
class A{
public void test(){
class B{
//局部内部类
}
}
}
特点:不允许使用static修饰局部内部类,和其中的任何成员,要使用该类必须作为返回值return出去
匿名内部类 (很常用)
什么是匿名内部类
字面意思就是没有名字的类
通过new 实例化接口或是抽象类,同时提供相应的方法实现
实例:
interface USB{
void open();
}
class PC {
public void working(){
//此处需要一个实现了USB接口的对象来完成操作
//我们可以定义一个类实现USB接口先下面的A类一样
//然后实例化A对象来完成OPEN操作
new A().open();
//我们也可以使用匿名内部类的方式来简化操作
new USB(){
public void open(){
System.out.println("OPEN......");
}
}.open;
}
}
class A implements USB{
public void open(){
System.out.println("OPEN......");
}
}
在上面的例子中我们可以看出匿名内部类有以下优点:
-
代码更加紧凑
-
使用更便捷
当然匿名内部类的缺点也很明显:
- 这个类产生的对象也是没有名字的,只能在定义的地方使用一次
- 语法格式看起来比较乱
什么时候使用匿名内部类
1.临时需要某个接口或是抽象类的独享完成某个功能
2.当整个任务的部分代码已经完成,但是剩下一部分关键代码需要由使用这个功能的人来完成
案例:
PC类的working方法需要传入一个USB接口对象才能完成整个任务
interface USB{
void open();
}
public class PC {
public static void main(String[] args) {
new PC().working(new AP());//以前的方式:定义类实现方法然后实例化
new PC().working(new USB() {//匿名内部类的方式:在当前位置完成上述全部操作
@Override
public void open() {
System.out.println("open on NonNameClass");
}
});
}
public void working(USB usb){
usb.open();
}
}
class AP implements USB{
public void open(){
System.out.println("OPEN......on AP");
}
}
PS:个人觉得匿名内部类+匿名对象其实 与OC中的block,python中的函数对象要完成的事情是一样的,即方法的使用者实现部分代码,然后作为参数传给某个方法(本质就是回调机制
),但是JAVA中定义方法必须借助类,所以才有了匿名内部类这么一说,归根结底是因为JAVA强制面向对象导致的;
补充:匿名内部类中可以使用构造代码块