Java面向对象OOP
1.面向过程和面向对象
1.1面向对象思想:
- 分类的思维模式
- 思考问题首先考虑解决问题需要哪些分类
- 然后对这些分类进行单独思考
- 最后才对某个分类下的细节进行面向过程的思索
- 适合处理复杂的问题
- 适合处理需要多人协作的问题
对于描述复杂的事务,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是具体到微观操作,仍然需要面向过程的思路去处理。
1.2OOP本质
以类的方式组织代码,以对象的组织(封装)数据。
从认识论角度考虑,现有对象后有类。对象,是具体的食物。类,是抽象的,是对对象的抽象。
从代码运行角度考虑,先有类后有对象。类是对象的模板。
对象是抽象的具体实例
2.构造器
使用new关键字创建对象的时候,本质是在调用构造器,初始化对象的值。除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
- 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
- 一旦定义了有参构造,无参就必须显式定义,否则无参不会默认生成,无参构造器调用不到
- alt+insert自动生成快捷键,列表中有constructor,可自动生成构造函数。
3.创建对象内存分析
public class Application {
public static void main(String[] args) {
Pet dog=new Pet();
dog.name="旺财";
dog.age=3;
dog.shout();
System.out.println(dog.name);
System.out.println(dog.age);
Pet cat=new Pet();
}
}
public class Pet {
public String name;
public int age;
//无参构造
public void shout(){
System.out.println("叫了一声");
}
}
3.1程序开始执行
将主类加载进方法区,静态方法随类一起加载,字符串加载进常量池,age是一个int类型的数字。
方法区是堆内一个特殊区域
3.2接下来执行main方法,
main在栈底,栈里所有东西结束时,main方法从栈里弹出,程序结束。栈中一些方法,和一些对象的引用。
3.3mian方法走到第一步,new Pet()
Pet类被加载,里面name默认null,age默认0
3.4生成一个具体对象 dog
栈里dog是一个引用变量名,真正对象在堆里
3.5对象里面的属性赋值
把“旺财”丢给了name,所以name才有了值。字符串保存在常量池中。
接下来给age赋值
3.6接下来Pat cat=new Pet()
3.7注意点
静态方法区。静态方法随类加载而加载,好处是,所有对象可以直接调用。
对象的引用:除基本数据类型之外,都可以叫做引用数据类型。对象是通过引用来操作的:栈—>堆
默认初始化:
- 数字:0 0.0
- char:u0000
- boolean: false
- 引用:null
4.封装
-
程序设计要追求“高内聚,低耦合”。
- 高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅暴露少量的方法给外部使用。
-
封装(数据的隐藏)
- 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
- 属性私有,set/get
-
好处
- 提高程序的安全性,保护数据。set中可以自定义一些验证方法
- 隐藏代码的实现细节
- 统一接口。所有的方法都是get、set形成了一个规范
- 提高了系统的可维护性。良好的封装利于修改内部代码
5.继承
- 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
- extends的意思是“扩展”。子类是父类的扩展。
- Java中类只有单继承,没有多继承。
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。
- 子类和父类之间,从意义上讲应该具有“is a”的关系。
- ctrl+H快捷键继承树。
- Java中,所有类,都默认或间接继承Object类。
- this当前类中,super父类中,普通方法调用的也是当前类中的。私有的东西无法被继承。
- 子类构造器中,隐藏代码,super()。因此new 一个子类时,会默认先执行父类的无参构造方法。且调用父了的构造器时,super(),代码必须写在子类构造器方法的第一行。构造器调用构造器时,this、super都要放在构造器代码的第一行。
- super必须智能出现在子类的方法或者构造方法中。
- super和this不能同时调用构造方法。
5.1重写
-
重写都是方法的重写,和属性无关。
-
父类的引用指向了子类
-
B b=new A(); b.test();
-
-
方法的调用只和左边,定义的数据类型有关
-
A a=new A(); a.test;
-
-
@Override重写
-
子类不能重写父类中声明为final或static或private的方法。
-
静态方法是类的方法,非静态方法是对象的方法,
5.1规则
- 方法名必须相同
- 参数列表必须相同
- 修饰符,范围可以扩大,但不能缩小
- public>protected>default>private
- 抛出的异常范围可以被缩小,不能扩大
- ClassNotFoundException–>Exception(较大)
- 子类的方法和父类必要一致,方法体不同。
5.2为什么需要重写
- 父类的功能,子类不一定需要,或者不一定满足
6.多态
-
动态编译:类型的最终转态只有在执行过程中才确定,类型:可扩展性
-
同一方法可以根据发送对象的不同而采用多种不同的行为方式。
-
一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多
-
Student extends Person //一个对象的实际类型是确定的 //new Student(); //new Person(); /*可以指向的引用类型就不确定了,父类的引用指向子类*/ Student s1=new Student(); Person s2=new Student(); Object s3=new Student(); //子类重写了父类的方法,执行子类的方法 //子类Student重写了父类的run方法 s1.run();//执行子类的 s2.run();//执行子类的 //都有run,程序执行时再确定执行哪个 //父类中没有eat方法 s1.eat();//执行 s2.eat();//报错,父类中无此方法((Student)s2).eat(),强制转换后可以 //结论:对象能执行哪些方法主要看左边Person s2的类型,与右边关系不大
-
6.1存在的条件
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象
6.2注意事项
- 多态是方法的多态,属性没有多态
- 父类和子类,有联系,否则类型转换异常!(ClassCastException)
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象,以下不能重写
- static方法,属于类,不属于实例
- final 修饰的
- private私有方法
6.3Instanceof
运行时,指出对象是否是特定类的一个实例。返回一个布尔值,这个对象是否,是这个特定类,或者是它的子类,的一个实例
- result = object instanceof class
- Result:布尔类型
- Object:必选项。任意对象表达式
- Class:必选项。任意已定义的对象类。
- 如果 object 是 class 的一个实例,则 instanceof 运算符返回 true
x Instanceof y;//xy之间没有父子关系就会编译报错
//true或者false是看x所指向的实际类型是不是y的子类型,
//如果x指向的类型跟y是有关系的,编译也会通过
//Object>String
//Object>Person>Teacher
//Object>Person>Student
Object object=new Student();
sout(object instance of Student);//true
sout(object instance of Person);//true
sout(object instance of Object);//true
sout(object instance of Teacher);//false
sout(object instance of String);//false
Person erson=new Student();
sout(erson instance of Student);//true
sout(erson instance of Person);//true
sout(erson instance of Object);//true
sout(erson instance of Teacher);//false
sout(erson instance of String);//编译报错,两者没有半毛钱关系
sout(Student instance of Teacher);//编译报错,同级
Student tudent=new Student();
sout(tudent instance of Student);//true
sout(tudent instance of Person);//true
sout(tudent instance of Object);//true
sout(tudent instance of Teacher);//编译报错,同级
sout(tudent instance of String);//编译报错
7.static关键字
7.1存在意义
- 主要意义,创建独立于具体对象的域变量或者方法。即使没有创建对象,也能使用属性和调用方法!
- 比较关键的作用就是 用来形成静态代码块以优化程序性能,在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次
7.2特点
- static修饰的变量或者方法不属于任何一个实例对象,而是被类的实例对象所共享
- 该类第一次加载的时候,就会加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的
- static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。
- 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问
7.3应用场景
如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量。
-
修饰成员变量
-
修饰成员方法
-
静态代码块
-
修饰类【只能修饰内部类也就是静态内部类】
-
静态导包
-
import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
-
7.4静态变量与实例变量
-
静态变量:
静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。 -
实例变量:
每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。 -
访问方法
- 静态变量
- 类名.静态变量
- 对象.静态变量(不推荐)
- 静态方法
- 类名.静态方法
- 对象.静态方法(不推荐)
- 静态变量
7.4注意事项
- 静态只能访问静态。
- 非静态既可以访问非静态的,也可以访问静态的。
8.抽象类
- abstract修饰符可以用来修饰方法也可以用来修饰类,抽象方法、抽象类。
- 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
- 抽象类,不能使用new关键字来创建对象,它是用来让子类继承的。
- 抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
- 子类继承抽象类,就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
- 抽象类有构造器。
9.接口的定义与实现
9.1普通、抽象、接口
- 普通类:只有具体实现
- 抽象类:具体实现和规范(抽象方法)都有
- 接口:只有规范,自己无法写方法,约束和实现分离,面向接口编程
9.2接口定义
- 接口就是规范,定义的是一组规则,体现的是“如果你是,,则必须能,,”的思想。
- 就扣的本质是契约,制定后大家都遵守
- OOP的精髓,是对象的抽象,最能体现这一点的就是接口
- 设计模式所研究的,实际上就是如何合理的去抽象,因此讨论设计模式都会针对具备了抽象能力的语言
培养自己的抽象思维。
9.3接口的作用
- 约束
- 定义一些方法,让不同的人实现
- public abstract
- public static final
- 接口不能被实例化,接口中没有构造方法,接口中不可以有构造方法(抽象类中可以有)
- implements可以实现多个接口
- 必须要重写接口中的方法
10.内部类
10.1成员(普通)内部类
-
public class Outer{ public class Inner{} }
-
依赖外部类对象而存在
-
成员内部类中,不能定义静态成员
10.1.1外部类调用内部类
//怎样调用成员变量,就可以怎样调用成员内部类, //(是指,非静态可以直接调用静态,静态不可直接调用非静态这方面) public class Outer{ //成员内部类 public class Inner{public void in();} //外部类的非静态方法,访问成员内部类 public void out(){ Inner inner=new Inner(); inner.in(); } //外部类的静态方法,访问成员内部类 public static void outSta(){ Outer out=new Outer(); Inner inner=out.new Inner(); inner.in(); } }
10.1.2内部类调用外部类
public class Outer{ private int id=10; public void out(){System.out.println("这是外部类的方法");} public class Inner{ public void in(){System.out.println("这是内部类的方法");} //获得外部类的私有属性 public void getId(){ System.out.println(id);//10 } //调用外部类的方法 Outer.this.out(); } }
10.2静态内部类
-
public class Outer{ static class Inner{} }
-
不需要依赖其外部类对象
-
静态内部类中也无法访问外部类的非静态成员
-
静态内部类可以用public,protected,private修饰
-
外部类可以访问内部类对象所有访问权限的成员
-
详解:外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,和普通内部类相同。
10.2.1外部类调用内部类
//静态内部类 public static class Inner{public void in();} //能怎样调用静态属性就能怎样调用静态内部类 new Outer.Inner().in();//类. 调用方式
10.2.2内部类调用外部类
public static class Inner{} //此时,System.out.println(id),获取不到id,解决方法,id修改为静态
10.3局部内部类
-
public class Outer{ public void out(){ public class Inner{} } }
-
其声明在一个方法体 / 一段代码块的内部,
-
在局部内部类前不加修饰符public和private,其范围为定义它的
代码块
-
不在定义类的定义域之内便无法使用,只能在out()方法中使用
-
可以任意访问该方法out()内的局部变量
-
其提供的功能使用匿名内部类都可以实现,而本身匿名内部类可以写得比它更简洁,
-
因此局部内部类用的比较少
10.4匿名内部类
-
是局部内部类的简要写法,可以在不声明类名的情况下,继承其他类并创建对象
-
作用范围和局部内部类完全一致
-
有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法了
-
// 1自定义接口 interface Inner { void in(int i); } private void demo() { // 在这个过程中会新建一个匿名内部类对象, // 这个匿名内部类实现了Inner 接口并重写 in 方法 Inner inner = new Inner() { // 可以在内部类中定义属性,但是只能在当前内部类中使用, // 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名, // 也就无法创建匿名内部类的对象 public void in(int i){ System.out.println("demo") } }
-
//2新建一个类对象 // new Object() 过程会新建一个匿名内部类,继承于 Object 类, // 并重写了 toString() 方法 clickListener.onClick(new Object() { @Override public String toString() { return "hhh"; } });
10.5总结
-
静态内部类,作用范围,就像静态变量一样
- 成员内部类,作用范围,就像成员变量一样
- 局部、匿名内部类,作用范围,就像局部变量一样
-
访问范围可以由内而外,不可,由外而内
-
局部内部类,可以访问,静态属性、静态内部类
-
静态内部类,访问不了,局部内部类
-
10.6使用情况
当不想让这个类,被外部访问时,可以使用内部类。对内,想要访问某些内部数据时,就得用到内部类,比如一些类的私有属性。
- 只想让类内部使用,就可以定义一个静态内部类
- 只是某个方法需要用到,就可以使用匿名内部类
在实际开发中,用的最多的就是静态内部类、匿名内部类。
Java8可以将匿名内部类写成lambda表达式,能够极大简化代码。