面向对象
什么是面向对象?
面向对象(Object-Oriented,简称OO)是一种编程方法,它将数据和对数据的操作封装在一个对象中,使得程序更易理解、修改和扩展。面向对象思想认为,计算机程序应该被视为一组对象的集合,每个对象都应该能够接收其他对象发来的消息,并能根据这些消息执行相应的动作。
在面向对象编程中,对象是一个具有特定属性和方法的实体。属性描述对象的状态,方法则用来操作这些状态。对象可以看做是现实世界中的事物,具有自己的属性和行为,这种设计方式符合人们对事物的认知方式。
通过面向对象编程,我们可以创建抽象的数据类型,并定义其属性和方法。这样做可以隐藏数据的具体实现细节,从而使得程序更加模块化,降低了耦合度,提高了代码的可维护性和可重用性。
常见的面向对象编程语言包括Java、C++、Python等,这些语言可以很好地支持面向对象编程范式,通过类和对象的概念来管理数据和操作。
面向对象优点
- 将复杂的问题简单化
- 更贴合与生活中人们的思维角度
- 面向过程更适合执行者的思考方式,面向对象适合于管理者的思考方式
面向对象与面向过程
面向对象(Object-Oriented,简称OO)和面向过程(Procedural-oriented,简称PO)是两种不同的编程范式。
在面向过程编程中,程序被看做是一系列可执行的步骤,这些步骤依次被执行,流程式地推进。程序主要由函数、数据结构等组成,函数对数据进行操作。在面向过程编程中,重点是函数的设计和实现,数据则是通过参数传递给函数。
而在面向对象编程中,程序被设计为一组相互关联的对象集合,每个对象都具有特定的属性和行为,并且能够与其他对象进行通信。程序主要由类及其实例对象组成,类描述了对象的属性和行为,实例对象则是类的具体化。在面向对象编程中,重点是对象的设计和建模,通过使用继承、封装和多态等机制来组织程序逻辑。
因此,面向过程和面向对象的主要区别在于思想方式和实现方法。面向过程重视步骤和函数,通过分解问题并设计函数来解决问题;面向对象则更注重对象和类之间的关系,将程序中的各种元素组织成一个有机整体,在实现上更加灵活和可扩展。同时,面向对象的程序具有更高的复用性、可维护性和可读性。
当然,这两种编程方式都有其优缺点,具体应该根据问题的实际情况来选择合适的编程方式。
总结:
- 都是一种思想,思维方式,思考问题的角度
- 面相过程 : 关注"我该如何实现",然后一步一步实现
- 面相对象 : 关注"我该找谁实现",不关注实现步骤,只关注这个"谁",'谁'就是对象
如何通过面向对象思想进行编程
将程序与现实生活紧密联系
对象|实例 : 描述现实生活中的真实存在的事物
类 : 定义一系列对象的共性-->图纸|模版|实体类
先有类,再根据类创建对象
类与对象的概念
在面向对象的编程中,类和对象是两个重要的概念。
类(Class)是一种抽象的数据类型,它描述了一组具有相同属性和方法的对象集合。类可以看做是一种模板或者蓝图,它定义了一组数据结构和操作这些数据的方法。
对象(Object)是类的一个实例,它具有类定义的属性和行为。在面向对象编程中,我们通过创建对象来使用类中定义的方法和属性。
类与对象的关系
- 类定义了一组属性和方法,对象包含了这些属性值和方法实现。
- 对象通过类来创建,一个类可以创建多个对象。
- 在改变一个对象的属性值或调用一个对象的方法时,不会影响其他对象或类本身。
类与对象的区别
抽象程度不同
类是一种抽象的数据结构,它描述了一组对象的共同特征,包括属性和方法等。而对象则是类的具体化,它包含了类定义的属性值和方法实现,是具体的实体。
内容不同
类包含了属性和方法的定义,但没有具体的值或实现。而对象则是具有这些属性值和方法实现的实体,是一个具体的实例。
使用方式不同
类是一种模板或者蓝图,它用于创建对象、描述对象的属性和行为等,是对实现进行了封装和抽象的结果。而对象则是类的具体实现,通过对象可以直接访问类中定义的属性和方法。
存储位置不同
类通常在代码中被定义,是静态的,存储于磁盘上;而对象则是在程序运行时创建的,存储于内存中。
可变性不同
类是一种不可变的数据结构,它定义了一组属性和方法,但不能直接改变这些属性和方法的定义。而对象则是可变的,可以修改它们的属性值或实现方法。
综上所述,类是一种描述共同特征的抽象数据类型,而对象是类的一个具体实例,包含了类定义的属性值和方法实现。类和对象之间的关系是类定义了对象的共同特征,而对象实现了这些特征。两者的使用方式、存储位置、可变性等方面也有所不同。
自定义类与对象
自定义类
定义类(类的组成)
属性 field
方法 method
构造方法 constructor
其他:代码块 静态代码块 内部类
属性(field 成员变量)
属性用于定义该类或该类对象包含的数据或者说静态特征。
属性作用范围是整个类体。
在定义成员变量时可以对其初始化,如果不对其初始化,Java使用默认的值对其初始化
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0.0 |
字符型 | '\u0000' |
布尔型 | false |
所有引用类型 | null |
属性定义格式:
[修饰符] 属性类型 属性名 = [默认值] ;
public class Person{
String name = "张三";
}
功能(方法)
方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。方法很类似于
面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,
整个程序的基本单位是类,方法是从属于类和对象的。
方法定义格式:
[修饰符] 方法返回值类型 方法名(形参列表) {
// n条语句
}
public class Person{
//成员变量
String name;
//成员方法
public void study(){
System.out.println("一个程序猿正在努力工作...");
}
}
注意:
java是值传递 :
基本数据类型 : 数据值
引用数据类型 : 对象的地址值
方法的形参类型可以为任意数据类型,包含自定义的引用数据类型
方法的返回值类型可以为任意数据类型,包含自定义的引用数据类型
实例化对象
创建对象 : new创建对象
new创建对象步骤
- 在堆内存中为对象开辟空间,成员变量跟随对象进入堆内存,并附默认值
- 调用构造器为对象初始化想信息
- 将地址返回给引用
类名 对象名 = new 类名();
引用数据类型 对象名|引用名|变量名 = new 引用数据类型();
Person p1=new Person();
调用类的属性和方法
对象名.成员变量 | 引用.属性名
对象名.成员方法 | 引用.功能名字(实参)
p1.name = "李四";
p1.study();
类的实例
一般类中有三种常见的成员:属性field、方法method、构造器constructor。这三种成员都可以定义零个或多个。
编写简单的学生类:
public class Student {
//属性(成员变量)
int id;
String sname;
int age;
//构造方法
Student(){
}
//方法
void study(){
System.out.println("我正在学习!");
}
}
成员变量与局部变量的区别
声明位置不同:
成员变量 :
类中方法外局部变量 :
方法中(块中)
作用范围不同:
成员变量 :
当前类中局部变量 :
当前方法不同的方法中即使有同名的局部变量,没有关系,互不影响,建议相同
内存存放的位置的:
成员变量 :
堆内存中局部变量 :
栈内存中
默认值情况不同:
成员变量 :
有默认值局部变量 :
没有默认值
class MyClass { // 实例变量的作用域
// 实例变量的声明
public void aMethod(方法形参) { // 形参的作用域
// 局部变量的作用域
// 局部变量声明
}
}
类与类之间的关系
类之间大体分为6种关系:在设计模式中类与类之间的关系主要有6种:依赖、关联、聚合、组合、继承,实现, 它们之间的耦合
度依次增加。
继承关系(Inheritance)
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加
它自己的新功能的能力。在Java中继承关系通过关键字extends明确标识。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdMVqkFM-1681228352165)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681111495686.png)]
public class Person {
String name;
int age;
void eat(){}
void say(){}
}
//继承
class Student extends Person{
void study(){}
}
class Teacher extends Person{
void teach(){}
}
实现关系
实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。
在Java中此类关系通过关键字implements明确标识。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLZFTCck-1681228352166)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681114862227.png)]
interface Vehicle{
void move();
}
class Ship implements Vehicle{
void move(){
System.out.println("水里移动..");
}
}
class Car implements Vehicle{
void move(){
System.out.println("陆地跑..");
}
}
依赖关系(Dependency)
依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非
常弱的,但是类B的变化会影响到类A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是
依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNDg7QJA-1681228352167)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681115112102.png)]
public class Person {
public void drive(Car car){} //方法参数
}
class car{}
关联关系(Association)
关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存
在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可
以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类
A引用了一个类型为被关联类B的全局变量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SmurAWG0-1681228352167)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681118127051.png)]
public class Person {
public Address address;
//关联关系
public void setAddress (Address address){
this.address= address;
}
public Address getAddress (){
return address;
}
}
class Address{}
聚合关系(Aggregation)
单向,关联关系的一种,与关联关系之间的区别是语义上的,关联的两个对象通常是平等的,聚合则一
般不平等,有一种整体和局部的感觉,实现上区别不大。表现在代码层面,和关联关系是一致的,只能
从语义级别来区分。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SUnTyUvt-1681228352167)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681118815660.png)]
public class Team {
public Person person;
public Team(Person person){
this.person = person;
}
}
Class由Student组成,其生命周期不同,整体不存在了,部分依然存在,当前Team解散了,人还在,
还可以加入别的组。
组合关系(Composition)
单向,是一种强依赖的特殊聚合关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mp2hc1w4-1681228352168)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681119222709.png)]
public class Person {
public Head head;
public Body body;
public Person(){
head = new Head();
body = new Body();
}
}
Head,Body,Arm和Leg组合成People,其生命周期相同,如果整体不存在了,部分也将消亡。
面对对象内存分析
内存分析
Java中的对象和数组是通过引用对其操作的
引用可以理解为一种受限的指针
指针是可以进行与整数做加减运算的,两个指针之间也可以进行大小比较运算和相减运算。引用不
行,只能进行赋值运算。引用就是一个变量或对象的别名(引用的本质是一个对象);指针是一个段内存空间的地址(指向存
储一个变量值的空间或一个对象的空间)
内存划分 :
栈
存放:局部变量
先进后出,自下而上存储
方法执行完毕,自动释放空间
堆
存放new出来的对象
需要垃圾回收器来回收
方法区
存放:类的信息(代码)、 static变量、字符串常量等
构造方法(构造器constructor)
构造器也叫构造方法(constructor),用于对象的初始化。构造器是一个创建对象时被自动调用的特殊方
法,目的是对象的初始化。构造器的名称应与类的名称一致。Java通过new关键字来调用构造器,从而返回该类的实例,是一种特殊的方法。
声明格式
[修饰符] 类名(形参列表){
//n条语句
}
public Dog(){}
构造器4个要点
构造器的方法名必须和类名一致!
构造器虽然有返回值,但是不能定义返回类型(返回值的类型肯定是本类),不能在构造器里调用return。
通过new关键字调用!!
如果我们没有定义构造器,则系统会自动定义一个无参的构造方法。如果已定义则编译器不会添加无参数构造方法!
与普通方法一样,构造方法也可以重载
修饰符 final abstract static不能用来修饰构造器
作用
初始化对象信息的,不是用于创建对象的
空构造
没有参数构造器叫做无参构造
一个类中,如果没有显式|手动 加入 任意构造器 javac 编译后 自动加入空构造 一旦加入构造器 ,javac 不会加入空构造器。
class Dog{
String color;
String name;
//空构造
//public Dog(){} 默认提供
}
重载
一个类中可以存在多个构造器
方法重载: 两同三不同 同一个类方法名相同,形参类型 个数 顺序不同 构造器重载: 只看形参即可 形参类型 个数 顺序不同。调用时按“就近最优原则”
class Dog{
String color;
String name;
//空构造
Dog(){}
public Dog(String name,String color){
this.name=name;
this.color=color;
}
public static void main(String[ ] args) {
Dog d1 = new Dog();
Dog d2 = new Dog("小白", "白色");
Dog d3 = new Dog("小灰", "灰色");
}
}
this与static
this关键字
this 即”自己”,代表对象本身,谁调用代表谁 | 指代当前对象(new的对象|调用成员方法的对象)
在成员方法中或构造器中隐式的传递。作用如下:
- this.属性避免属性和形参、局部变量同名,发生就近原则
- this([实参列表]):构造器的首行调用本类中其他构造器。
- 不存在同名问题,哪里有就匹配哪里,如果有局部找局部,如果有成员调用成员,默认相当于省略了
- 存在同名问题,就近原则找局部(通过this.调用成员)
class Web{
//网址
public String url;
public Web(){
System.out.println("构造器");
}
public Web(String url){
this(); //this()构造器的调用
this.url =url;//this.属性 区分同名问题
}
//赋值
public void setUrl(String url){
this.url =url;
}
/**
属性与形参 同名,发生就近原则
public Web(String url){
url =url;
}
//二者不同名
public Web(String u){
url =u;
}*/
}
注意:静态方法中无this。
多个构造器之间不能使用this(实参)相互调用
static关键字
凡是静态的都是属于类的,与对象无关的,先于对象存在的。可用于修饰属性、方法、块。
在类中,用static声明的成员变量为静态成员变量 ,或者叫做: 类属性,类变量。
- 它为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
- 对于该类的所有对象来说,static成员变量只有一份。被该类的所有对象共享!!
- 可以使用”对象.类属性”来调用。不过,一般都是用“类名.类属性”
- static变量置于方法区中!
用static声明的方法为静态方法
- 不需要对象,就可以调用(类名.方法名)
- 在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可直接访问非static的成员,
但是可以通过对象使用。- 静态方法不能以任何方式引用this和super关键字
static修饰成员位置的块 : 静态块
static修饰成员位置的类 : 静态的内部类
class StaticDemo01 {
static int id;//静态变量
int age; //成员变量
public static void main(String[] args) {
//静态的内容可以直接使用
System.out.println("id1="+id); //id1=0
System.out.println("id2="+StaticDemo01.id); //id2=0
test();
//通过对象使用成员
StaticDemo01 sd=new StaticDemo01();
System.out.println("age="+sd.age);//age=0
}
/*
* 静态方法
*/
public static void test(){
id++;
}
/*
* 成员方法 使用静态和非静态的内容
*/
public void test2(){
//非静态的方法中可以直接使用成员内容,可以通过对象使用
age++;
id++;
}
}
注意
- 静态内容中只能直接使用静态内容,通过对象使用成员内容
- 成员内容中可以直接使用静态内容,可以直接使用成员内容,也可以通过对象使用
注意:
成员是属于对象的,需要通过对象使用成员,因为成员变量在对象的堆内存中,先有对象才有成员
static是静态的,静态的内容是属于类的,静态变量在静态区中,是独一份的,所有对象共享的
加载初始顺序 : 先静态后成员
静态变量在类第一次加载完成之后就初始化,在静态区中,独一份
调用特点
在静态环境中,可以直接调用静态内容,不能直接调用成员,需要依赖对象调用
在成员环境中,可以直接调用静态,可以直接调用成员(非静态)
分类 :
定义在成员位置的变量: 成员变量
被static修饰的成员变量 : 类变量|静态变量 不被static修饰的成员变量 : 实例变量
定义在成员位置的方法 : 成员方法
被static修饰的成员方法 : 静态方法|类方法 不被static修饰的成员方法 : 实例方法
block与debug
block
块{},在java中自成作用域,可以分为
静态代码块 | 构造代码块 | 普通语句块 | 同步代码块 | |
---|---|---|---|---|
声明位置 | 类中,方法外 | 类中,方法外 | 方法中 | fynchronized() |
作用 | 整个类进行某些初始化操作(静态属性赋值...) | 构造代码块是为对象初始化操作(为静态或非静态成员属性赋值...) | 声明一些临时变量等.. | 控制并发 |
执行时机 | 类第一次加载时,只执行一次,如果多个静态块,从上到下依次执行 | 创建对象时,执行构造器代码之前执行,如有多个,从上到下依次执行 | 跟随方法执行 | 跟对方法执行 |
注意:
同一作用域内,变量不能同名
构造块代码在编译期间,会被编译到要执行的构造器内部,构造器代码之前
如果存在多个构造块,从上到下依次执行
静态块在类第一次加载完成之后就执行,并且只执行一次,不能手动调用
如果存在多个静态块,从上到下依次执行
注意
- 类第一次被载入时先执行static代码块;类多次载入时,static代码块只执行一次;static块经常用
- 来进行static变量的初始化。
- 是在类初始化时执行,不是在创建对象时执行。
- 静态初始化块中不能访问非static成员。
- 构造块被被编译到将要执行的构造器代码之前执行
静态块,仅在类的第一次使用时加载。
构造块,先于构造器执行,每创建一个对象执行一次
debug
在学习或开发过程中,遇到bug是避免不了的,为了能够快速调试,可以使用debug调试工具。
调试一个Java程序非常简单的,主要有设置断点、启动调试、单步执行、结束调试几步。
作用
追踪程序的执行流程
观察程序执行过程中变量的变化情况
快速定位问题出现的位置|异常出现的位置
学习第三框架的源码
debug界面窗口
- 断点:在左边行号栏单击左键,断点行的颜色可自己去设置。
- Debug窗口:访问请求到达第一个断点后,会自动激活Debug窗口。如果没有自动激活,可以去设
置里设置。- Debugger->debug调试窗口,Console->控制台
- 调试按钮:调试的主要功能就对应着这几个按钮,鼠标悬停在按钮上可以查看对应的快捷键。
- 方法调用栈:这里显示了该线程调试所经过的所有方法。
- Variables:在变量区可以查看当前断点之前的当前方法内的变量。
- 服务按钮:可以在这里关闭/启动服务,设置断点等。
debug调试使用步骤
设置断点
启动调试
单步执行
调试按钮说明 Show Execution Point (Alt + F10):如果你的光标在其它行或其它页面,点击这个按钮可跳转 Step Over (F8):步过,一行一行地往下走,如果这一行上有方法不会进入方法。 Step Into (F7):步入,如果当前行有方法,可以进入方法内部,一般用于进入自定义方法内,不会进入官方类库的方法。 Force Step Into (Alt + Shift + F7):强制步入,能进入任何方法,查看底层源码的时候可以用这个进入官方类库的方法。 Step Out (Shift + F8):步出,从步入的方法内退出到方法调用处,此时方法已执行完毕,只是还没有完成赋值。 Drop Frame (默认无):回退断点,后面章节详细说明。 Run to Cursor (Alt + F9):运行到光标处,你可以将光标定位到你需要查看的那一行,然后使用这个功能,代码会运行至光标行,而不需要打断点。
- 结束调试
package与import
package
概述
package存在的意义是防止命名冲突造成使用不便。
包机制
- 管理项目中众多资源
- 提供多重的命名空间
package类似一个文件夹,文件夹内有各种文件。package与package的附属关系用“.”连接,类似父文件
夹中的子文件夹。比如说 java.lang.String就是java文件夹中的lang文件夹中的String文件。
java.io.InputStream则是java文件夹中的io文件夹中的InputStream文件。同一个文件夹内无法存在同名的文件,而不同名的文件夹里允许存在同名文件,因此不同文件夹(即不
同package中允许出现相同class名)。为了便于管理大型软件系统中数目众多的类,解决类的命名冲突问题,Java 引入包(package)机制,提
供类的多重类命名空间。
格式
一般的命名为:公司域名倒写+功能名|模块名。
package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指
定为无名包)。
package pkg1[.pkg2[.pkg3…]];
package com.java01.test;
Java 编译器把包对应于文件系统的目录管理,package 语句中,用’.’来指明包(目录)的层次,例如使
用语句: package com.java01 ,则该文件中所有的类位于.\com\java01 目录下
注意
不要定义与 jdk 相同的包,相同的类,否则会引起很多你觉得莫名其妙的问题
写项目时都要加包,不要使用默认包。
com.oop和com.oop.test,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者
是前者的一部分。
import
如果一个类存在包名,则在其他包下使用该类时,必须使用全额限定名(简称全名或完整类名,
com.java01.MyClass),编译器才能找到该类;也可以使用 import 在文件的开头引入要使用到的类。
import java.util.Scanner;
import java.io.*; //模糊匹配当前io包下所有类
public class ImportDemo {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in); //因为使用import关键字导包,可以使用
java.util.ArrayList list=new java.util.ArrayList(); //权限定名
//导入集合 注意:导入是不要导错包
//使用*,模糊匹配
File file=new File("D://test.txt");
InputStream isInputStream=new FileInputStream(file);
}
}
不需要使用 import 的类有:
- 语言包 (java.lang)下的类 (String,System...)
- 同包下的类
静态导入:
import 语句不仅可以导入类,还增加了导入静态方法的功能
//导入静态属性
import static java.lang.Math.PI;
import static java.lang.System.out;
//导入静态方法
import static java.lang.Math.sin;
public class ImportDemo {
public static void main(String[] args) {
out.println(PI);
sin(1.1);
}
}
总结
如果想将一个类放入包中,在类的源文件首行使用package
必须保证该类的 class 文件位于正确的目录下
另外的类想访问的话:
写全名
引入
模糊匹配(会将该包下所有用到的类引入进来),会降低编译速度,但是不会影响运行
速度
具体类名
静态导入
- 同包下的类不需要导入
JDK中常用的包简单介绍
- java.lang –语言包:语言中常用的功能,如:String、Math、System、Integer、Thread…
- java.util – 工具包:提供一些实用的工具类,如:容器(List、Set、Map…),日期类
- java.io – 输入输出包:提供操作读取文件的相关类,如:File、InputStream、OutputStream…
- java.net – 网络包: 操 作 远 程 资 源 的 类 , 如:InetSocketAddress、 DatagramPacket、
ServerSocket…- java.sql – 数据库包:操作JDBC的类,Connection、Statement、ResultSet….
垃圾回收机制(gc)
Garbage Collection 垃圾回收机制
每个程序员都遇到过内存溢出的情况,程序运行时,内存空间是有限的,那么如何及时的把不再使用的
对象清除将内存释放出来,这就是GC要做的事。
jvm内存结构分为五大区域:
程序计数器、虚拟机栈、本地方法栈、堆区、方法区。
其中虚拟机栈、本地方法栈与程序计数器这3个区域随线程而生、随线程而灭,因此就不需要考虑过多内存垃圾回收问题,因为一个方法调用结束或者线程结束时,内存自然就跟随着回收了。
我们就把重点放在方法区与堆区,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。GC主要做了清理对象,整理内存的工作。
不同语言下对象空间的释放:
传统的C/C++语言,需要程序员负责回收已经分配内存。显式回收垃圾回收的缺点:
程序忘记及时回收,从而导致内存泄露,降低系统性能。
程序错误回收程序核心类库的内存,导致系统崩溃。
Java语言不需要程序员直接控制内存回收,是由JRE在后台自动回收不再使用的内存,称为垃圾回收机制(Garbage Collection)。
可以提高编程效率。
保护程序的完整性。
其开销影响性能。Java虚拟机必须跟踪程序中有用的对象,确定哪些是无用的。
垃圾回收机制关键点
垃圾回收机制只回收JVM堆内存里的对象空间。
对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
现在的JVM有多种垃圾回收实现算法,表现各异。
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用
变量重新引用该对象,则会重新激活对象)。
永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
封装(encapsulation)
概念
我们程序设计要追求“ 高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干
涉;低耦合:仅暴露少量的方法给外部使用。利用抽象数据类型将数据和基于数据的操作封装在一起,
使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他部分只有通过包裹在数据外面的被授权的操作来与这个抽象数据类型交流与交互。也就是说,用户无需知道对象内部方法的实现细节,但可以根据对象提供的外部接口(对象名和参数)访问该对象。
作用
- 实现了专业的分工。将能实现某一特定功能的代码封装成一个独立的实体后,各程序员可以在需要
的时候调用,从而实现了专业的分工,即工作中的分模块、分功能开发。- 隐藏信息,实现细节。通过控制访问权限可以将可以将不想让客户端程序员看到的信息隐藏起来,
如某客户的银行的密码需要保密,只能对该客户开发权限。
JavaBean
在编写 javabean 中的有一些常用的规则如
- 类是公共的public
- 至少提供一个空构造
- 提供一对公共的访问方式属性尽可能私有化 private
- 设置器:setter 为私有属性设置值
- 访问器:getter 获取私有属性的值
public class Person { //姓名
private String name; //年龄
private int age; //性别
private boolean sex;
public Person() {
}
//setter与getter
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 boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
}
注意 : 以后所定义的所有实体类,属性都需要私有化,并提供一对公共的访问方式
面向对象-继承(inheritance)
继承
继承的本质 : 是对某一批类的抽象,从而实现对现实世界更好的建模。
继承的作用 : 使用继承可以提高代码的复用性。
如何使用继承 : 使用
extends
关键字即可。extends 关键字的意思是“扩展”。子类是父类的延续+扩展。
java 中使用 extends 关键字实现类的继承机制,语法规则
<modifier> class <name> [extends <superclass>]{}
//父类 class Person{ public String name; public int age; public void sleep(){ System.out.println("休息"); } }
//子类 //教师类 class Teacher extends Person{ public String subject; public Teacher() { } public void teach(){ System.out.println("传授知识"); } } //学生类 class Student extends Person{ public String school; public Student() { } public void study(){ System.out.println("在知识的海洋畅游!"); } }
注意
子类继承父类的成员变量和成员方法,但不继承父类的构造方法
java中只有单继承 ,一个子类只能存在一个直接父类
优点 : 简单
缺点 : 不便于后期维护
java中的多继承,可以通过接口来实现
如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
继承的特点
优点
通过继承可以简化类的定义,实现代码的重用|提高代码复用性
可以更好的扩展程序
子类一旦继承父类,可以有权使用父类中的成员,也可以扩展定义子类独有内容
java是单继承继承,实现简单
缺点
子类与父类之间紧密耦合(耦合度高),子类依赖于父类的实现,子类缺乏独立性。
不便于后期维护
单继承一个子类只能有一个父类,不够灵活,不便于后期维护
权限修饰符
public: 一览无遗;
protected:子承父业 (儿子自己使用);
default :家庭和睦;
private :占为已有
关键字 | 同一个类 | 同一个包中 | 不同包下子类 | 所以类 |
---|---|---|---|---|
private | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
public class YJXXT_Modefier{
private int privateField;
int privateField; // default 不能写
protected int privateField;
public int privateField;
}
注意 :
访问权限修饰符都是成员修饰符,只能修饰成员不能修饰局部
default是默认的,没有显示的通过其他访问权限修饰符进行修饰,默认通过default修饰
能够用来修饰类的访问权限修饰符 : public | default
访问被protected修饰的成员
1. 同包类下访问 2. 不同包下的子类中,通过继承关系访问
重写Override
父类的功能实现不满足子类的要求,可以在子类中按需改写,这就是方法的重写。
实现重写的三个条件
不同的两个类
继承关系|实现关系
方法签名相同
@Override:注解,强制检查是否为重写方法
注意
子类重写的方法会对父类的方法进行屏蔽。
当子类对象调用时,会调用子类中重写的方法,子类没有找父类。
public class OverrideTest { public static void main(String[] args) { Sicong sicong=new Sicong(); sicong.getMoney(); //调用子类中重写方法 } } //父类 class Jianlin{ public void getMoney(){ System.out.println("先定一个小目标,赚他个一个亿"); } } //子类 class Sicong extends Jianlin{ @Override //强制检查是否为重写方法 public void getMoney(){ super.getMoney();//在父类的原赚钱方法上扩展新功能,老功能不变 System.out.println("我认识的人都没我有钱"); } }
重写的三个"="
“==”:方法签名完全相等。
“≤”:返回值类型
基本数据类型|void : 子类中的重写方法的返回值类型==父类中被重写方法的返回值类型
引用数据类型 : 子类中重写方法的返回值类型<=父类中被重写方法的返回值类型
“≥”:访问权限修饰符
子类中重写方法的访问权限修饰符>=父类中被重写方法的访问权限修饰符
以下修饰符、修饰的内容不能重写:
- private修饰的方法不能被重写
- final修饰的方法不能被重写
- static修饰的方法不能被重写(子类如果出现和父类静态方法同名情况,那么子类中的方法也必须为静态的)
重写和重载的区别
实现重载条件 :
- 一个类中的多个方法
- 方法名相同
- 参数列表不同|方法签名不同
- 参数个数不同
- 参数类型不同
- 不同类型参数的顺序不同
实现重写的条件 :
- 两个类中的两个方法
- 继承|实现关系
- 方法签名相同
使用重写前提需求:当子类对从父类中所继承的功能满意,但是功能的实现不满意的时候,在子类中重写实现这个功能->重写
private修饰的成员是否能被继承? 能被继承但是无权访问
静态的内容是否能够被继承? 能
父类中的构造器是否能被继承? 不能
super关键字
super
super是指向父类的引用。
super可以在子类构造器中调用父类某个构造器
如果构造方法没有显示地调用父类的构造方法,那么编译器会自动为它加上一个默认的super()方法调
用。如果父类由没有默认的无参构造方法,编译器就会报错,super()语句必须是构造方法的第一个子句。
super可以用来区分子父类中同名成员
如果不存在同名问题,可以直接在子类中调用父类内容,super默认省略
如果存在同名问题,在子类中调用同名成员,默认this.恒源 调用当前子类同名成员,先要调用父类同名成员,
必须定义为super.成员
//父类
public class Animal {
int eye = 2;
public Animal(){
super();
System.out.println("动物");
}
public void run(){
System.out.println("动物有不同走路方式");
}
public static void main(String[] args) {
Bird b = new Bird();
b.run();
}
}
//子类
class Bird extends Animal{
public Bird(){
super();
System.out.println("鸟类");
}
public void run() {
super.run(); // 通过super可以用父类方法和属性
System.out.println("鸟是飞飞飞飞飞飞");
System.out.println("鸟类有"+super.eye+"只眼睛");
}
}
Bird--> Animal --> Object 图形分析如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPhgNyWB-1681228352169)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1681184580359.png)]
构造方法调用顺序
根据super的说明,构造方法第一句 总是:super(…)来调用父类对应的构造方法。
先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
this和super之间的区别
相同点
- this和super都能用来调动其他共构造器,都要在首行出现
- this和super都可以用来区分同名问题,不区分同名时候可以省略
- this和super都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,
static语句块。
不同点
- this(参数)构造器第一行调用本类中其他构造器,super(参数)构造器第一行调用父类中某个构造器
- this用来区分成员和局部同名问题,super用来区分子父类中同名问题
注意
this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造
函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意
义,编译器也不会过。从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字
final关键字
final 表示最终的。
final可以用来修饰变量,方法,类。
修饰变量,被final修饰的变量为常量
final int x=3; //x=4; 常量不能改变
修饰方法,被final修饰的方法不能被重写
final void eat() { … }
修饰类,被final修饰的类不能被继承(太监类)
final class Person{ … }
Object类
Object 类是所有 Java 类的根基类
如果在类的声明中未使用 extends 关键字指明其基类,则默认基类为 Object 类
toString
当打印对象的引用时,默认调用toString()方法
默认返回:包名+类名+@+哈希码(根据对象的内存地址生成,唯一不重复)
可以重写,实现义字符串的形式返回对对象(打印对象所有成员属性的值)
//Object类中的toString : public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
User p1=new User("张三",20); System.out.println(p1); //相当于 System.out.println(p1.toString());
当输出对象的字符串表现形式时候,想要输出的是对象的所有属性值而非地址值,这时可以在子类根据所有的属性值进行重写===>通过快捷键生成
注意 : 以后所定义的所有实体类都需要根据所有属性值重写toString方法
equals
比较相等是否相等
默认地址比较(”第一个盒子的比较”)
重写可以是实现比较两对象的内容是否一致
object1.equals(object2) 如 : p1.equals(p2) 比较所指对象的内容是否一样,具体看equals的方法重写 object1 == object2 如:p1==p2 比较p1和p2的值即内存地址是否相等,即是否是指向同一对象。
注意:自定义类须重写equals(),否则无法实现比较其内容
class User{ String name; int age; public User() { // TODO Auto-generated constructor stub } public User(String name, int age) { super(); this.name = name; this.age = age; } //重写toString @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } //重写equals @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } }
==与equals之间的区别
== 比较任意类型的数据是否相等
基本数据类型(数据值) : 比较就是数据值
引用数据类型(对象) : 比较对象的地址值
equals 比较引用数据类型的数据
如果使用Object类中equals方法的实现,默认根据==实现比较,比较对象地址值
如果调用子类中重写的equals方法,可以实现比较对象的内容(属性值)
多态(polymorphism)
概述
多态polymorphism,主要是用来实现动态联编的,换句话说,就是程序的最终状态只有在执行过程中才被决定而非在编译期间就决定了。这对于大型系统来说能提高系统的灵活性和扩展性。
一种事物的多种形态|多种表现形式-->行为多态(一个功能的不同实现方式)
引用变量的两种类型:
- 编译时类型(模糊一点,一般是一个父类)
- 由声明时的类型决定。
- 运行时类型(运行时,具体是哪个子类就是哪个子类)
- 由实际对应的对象类型决定
多态的存在要有3个必要条件:要有继承,要有方法重写,父类引用指向子类对象
注意 : 如果编译时类型和运行时类型不一致,就可能会造成多态。
使用
多态是方法的多态,属性没有多态性。
public class TestPolym {
public static void main(String[] args) {
//父类引用指向子类对象
Animal animal = new Dog();
System.out.println(animal.age); //属性调用时,仍然是基类的属性。属性没有多态!
animal.shout(); //行为存在多态
animalCry(new Dog());
//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
//如果没有多态,我们这里需要写很多重载的方法。如果增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
//有了多态,只需要增加这个类继承Animal基类就可以了。
animalCry(new Cat());
}
static void animalCry(Animal a){
a.shout();
}
}
class Animal {
int age=10;
public void shout(){
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
int age=28;
public void shout() {
System.out.println("旺旺旺!");
}
public void gnawBone(){
System.out.println("我再啃骨头");
}
}
class Cat extends Animal {
int age=18;
public void shout() {
System.out.println("喵喵喵喵!");
}
}
普通引用调用成员(成员变量|成员方法)
能够调用子类中的所有成员,以及子类从父类中继承的成员
多态调用成员(成员变量|成员方法) :
多态调用成员变量:编译运行看父类|左边|引用
多态调用成员方法:编译看父类|左边|类型,运行找子类|右边|对象
多态需要配合方法的重写才有意义,会调用子类中重写的方法对子类新增成员不可见
引用数据类型的类型转换(Cast)
标准语法格式 : 对应类型的数据赋值给对应类型的变量
数据类型转换 : 基本数据类型
自动类型提升 : 小 --> 大
强制类型转换 : 大 --> 小
小范围类型 变量 = (小范围类型)大范围类型数据;
转型 :引用数据类型
子类 : 小 父类 : 大
向上转型 : 小 --> 大
父类 引用 = new 子类(); --> 多态
向下转型 : 大 --> 小
子类 变量 = (子类)父类类型数据;
注意 : 当想要通过多态引用调用子类中新增成员时候,可以先向下转型,再调用
注意:
编写程序时,如果想调用运行时类型的方法,只能进行类型转换。不然通不过编译器的检查。但是如果两个没有关联的类进行强制转换,会报: ClassCastException 。比如:本来是狗,我把它转成猫。就
会报这个异常。
实现避免类型转换异常的出现引用 instanceof 类型 : 判断前面的引用是否是指向后面类型的对象|子类对象,如果是就返回true,不是就返回false
instanceof
使用前提
在向下转型的时候,为了避免类型转换异常 ClassCastException 的出现,可以使用instanceof进行判断。
instanceof是Java的一个二元操作符,和==,>,<是同一类。由于它是由字母组成的,所以也是Java的
保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。Animal animal = new Dog(); System.out.println(animal instanceof Animal); System.out.println(animal instanceof Cat); System.out.println(animal instanceof Dog); //先判断后转型 if(animal instanceof Dog){ Dog dog = (Dog) animal; }
抽象类与抽象方法
抽象类用来描述一种类型应该具备的基本特征与功能, 具体如何去完成这些行为由子类通过方法重写来完成。
抽象方法指只有功能声明,没有功能主体实现的方法。
具有抽象方法的类一定为抽象类。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
定义
抽象方法 : 不需要方法体的方法,需要用 abstract 关键字修饰。只能出现在抽象类中。
public abstract void shout();
抽象类 : 被 abstract 修饰的类为抽象类。
public abstract class Canine{ private String brand; public Canine(){} public Canine(String brand){ this.brand = brand; } //抽象方法 public abstract void shout(); //具体方法 public void sleep(){ System.out.println("闭着眼睛睡觉!!!"); } }
抽象类的使用要点
有抽象方法的类只能定义能抽象类
抽象类不能实例化,即不能用new来实例化抽象类。
抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
抽象类只能用来继承。
抽象方法必须被子类实现。
abstract不可以与private,final,static,native一起使用
抽象类和普通父类的区别
就在于包含抽象方法,抽象方法必须被子类实现,从而可以对子类的设计进
行规范。作用
实现了规范和具体实现的分离。通过 abstract 方法定义规范,然后要求子类必须定义具体实现。引用
变量仍然可以定义为抽象类,这样就可以规范地实现多态了。
接口
特殊的抽象类
抽象方法的结合
是一种引用数据类型
类只能单继承,接口可以多实现
定义开发规范
实现解耦(降低耦合度)
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。
全面地专业地实现了:规范和具体实现的分离。
抽象类还提供某些具体实现,接口 不提供任何实现 ,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。
从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。
如果能把你要设计的系统之间模块之间的接口定义
好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。大家在工作以后,做系统时往往就是使用 “面向接口” 的思想来设计系统。
类类 -> 一个类 继承 另外一个类
类抽象类 > 一个类继承另一个抽象类 (类要重写抽象方法)
类接口 -> 一个类实现若干个接口(类种需要重写所有接口种的所有方法)
接口接口 -> 一个接口继承若干个接口
定义接口
[权限修饰符] interface 接口名 [extends 父接口1,父接口2…] { 常量定义; 抽象方法定义; }
- 访问修饰符:只能是public或默认。
- 接口名:和类名采用相同的命名机制
- extends:接口可以多继承
- 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
- 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract.
接口的本质
接口就是
规范
,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是天使,
则必须能飞。如果你是汽车,则必须能跑。如果你好人,则必须干掉坏人;如果你是坏人,则必须欺负好人。接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
接口使用要点
子类通过 implements 来实现接口中的规范
接口不能创建实例,但是可用于声明引用变量类型。
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public 的。
接口中只能包含静态常量、抽象方法 ,不能有普通属性、构造方法、普通方法、静态块。
通过extends继承父类,通过implements实现接口
接口的使用 : 通过实现类对象调用
具体实现类 : 重写所有的抽象方法 + 按需新增
抽象实现类 : 按需重写 + 按需 新增
如果一个类需要同时继承父类实现接口,要求先继承后实现
类与接口之间,只能是类实现接口,可以多实现
类与类之间,只能单继承
接口与接口之间,可以多继承
public class Test { public static void main(String[] args) { Volant volant = new Angel(); volant.fly(); System.out.println(Volant.FLY_HIGHT); } } interface Volant { int FLY_HIGHT = 100; // 总是:public static final void fly(); //总是:public abstract void fly2(); } interface Honest { void helpOther(); } class Angel implements Volant, Honest{ public void fly() { System.out.println("我是天使,飞起来啦!"); } public void helpOther() { System.out.println("扶老奶奶过马路!"); } } class GoodMan implements Honest { public void helpOther() { System.out.println("扶老奶奶过马路!"); } } class BirdMan implements Volant { public void fly() { System.out.println("我是鸟人,正在飞!"); } } class Bird implements Volant { public void fly() { System.out.println("正在飞!"); } } class Plane implements Volant { public void fly() { System.out.println("正在飞!"); } } class Satellite implements Volant { public void fly() { System.out.println("正在飞!"); } } class Missile implements Volant { public void fly() { System.out.println("正在飞!"); } }
注
Java 8发布以后,可以给接口添加新方法,但是,接口仍然可以和它的实现类保持兼容。这非常重要,因为你开发的类库可能正在被多个开发者广泛的使用着。而Java8之前,在类库中发布了一个接口以后,如果在接口中添加一个新方法,那些实现了这个接口的应用使用新版本的接口就会有崩溃的危险。(java8中也不能直接完全避免这个问题)
接口中被实现的方法叫做default方法,用关键字default作为修饰符来标识。当一个类实现一个接口的时候,它可以实现已经在接口中被实现过的方法,但这不是必须的。这个类会继承default方法。这就是为什么当接口发生改变的时候,实现类不需要做改动的原因。
在java8中的接口中不仅增加了默认方法,还增加了静态方法。
定义一个或者更多个静态方法。类似于类中的静态方法,接口定义的静态方法可以独立于任何对象
调用。所以,在调用静态方法时,不需要实现接口,也不需要接口的实例,也就是说和调用类的静
态方法的方式类似。语法如:接口名字.静态方法名
。实现接口的类或者子接口不会继承接口中的静态方法。static不能和default同时使用。在java8中
很多接口中都增加了静态方法
总结
jdk8接口新增 :
- 静态方法
通过接口名.调用
- 默认方法 : 通过default修饰
通过实现类对象调用
jdk9接口新增 :
1.私有方法 : 被private修饰的方法
只能在默认方法中调用
接口是可以多继承的,通过逗号分割
继承与实现区别
一个类可以单继承一个父类,可以多实现多个接口
继承 : 一个类如果继承了一个父类,继承了父类的成员,有权使用父类的成员,拿过来直接使用,如果存在个别情况不满意,可以在子类中重写
实现 : 一个实现类如果实现了一个接口,拥有了接口带来的功能,需要先在实现类中重写抽象方法,重写之后再使用
面向接口编程
面向接口编程是面向对象编程的一部分
为什么需要面向接口编程? 软件设计中最难处理的就是需求的复杂变化,需求的变化更多的体现在
具体实现上。我们的编程如果围绕具体实现来展开就会陷入”复杂变化”的汪洋大海中,软件也就不
能最终实现。我们必须围绕某种稳定的东西开展,才能以静制动,实现规范的高质量的项目。接口
就是编程中最稳定的东东!面向接口编程可以让我把握住真正核心的东西,使实现复杂的多变的需
求成为可能。通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩
展性和和可维护性。面向接口编程的概念比接口本身的概念要大得多。设计难,在你没有写实现时就要想好接口,接口一变就乱套了,设计要比实现难!
内部类(innerclasses)
一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类。形式如下:
public class Outter{ // 外部类 class Inner{ // 成员内部类 } public void test(){ class Inner1{ // 局部内部类 } } }
外部类的修饰符: public , default
内部类的作用
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
- 内部类可以很好的实现细节隐藏。 一般的非内部类,是不允许有 private 与 protected 权限的,
但内部类可以。- 内部类拥有外围类的所有元素的访问权限,利用这个特性可以实现java中的多继承。
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部
类的类名和$符号 。- 内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态
成员变量。- 可以避免修改接口而实现同一个类中两种同名方法的调用
成员内部类
成员内部类可以访问外部类的所有成员变量和方法(无论静态、非静态、私有、非私有),自身没有静态成员。
内部类其实严重破坏了良好的代码结构,但为什么还要使用内部类呢?
内部类可以随意使用外部类的成员变量(包括private)而不用生成外部类的对象,这也是内部类的唯一优点
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。
在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
创建内部类实例
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
//必须通过Outter对象来创建
Outter.Inner inner1 = outter.new Inner();
//第二种方式:
Outter.Inner inner2 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
成员内部类私有化
如果一个内部类只希望被外部类中的方法操作,那么可以使用private 声明内部类,此时我们必须在
Out 类里面生成 In 类的对象进行操作,而无法再使用 Out.In in = new Out().new In() 生成内部类
的对象也就是说,此时的内部类只有外部类可控制;如同是,我的心脏只能由我的身体控制,其他人无
法直接访问它。
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类
它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
只能访问方法中定义的 final 类型的局部变量(初始化之后便不能更改), final 可以不写,编译之后会变为final。
只能在方法内部使用。
void test(){ int a = 10; class Inner{ void test(){ System.out.println(a); } } Inner inner = new Inner(); inner.test(); }
静态内部类
当使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围对象,可以将内部
类声明为 static 。如果用 static 将内部类静态化,那么内部类就只能访问外部类的静态成员变量,
不能直接访问外部类的实例变量、实例方法,只有通过对象引用才能访问。其次,因为内部类被静态化,因此 Out.In 可以当做一个整体看,可以直接new 出内部类的对象(静态
内部类不通过外部类实例进行创建对象)生成静态内部类对象的方式为:
public class Test { public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } } class Outter { public Outter() { } static class Inner { public Inner() { } } }
匿名内部类
定义类的最终目的是创建一个类的实例,但是如果某个类以及它的实例只是用一次,则可以将类的定义与类的创建,放到与一起完成,或者说在定义类的同时就创建一个类 , 以这种方法定义的没有名字的类成为匿名内部类。
用于只创建这个类的一个对象的场合(不必命名),通常是作为方法的参数,隐式地继承了一个父类或者实现了一个接口。或直接使用接口引用指向匿名子类对象。
Outter o = new Outter(){};
①匿名内部类可以继承一个类或实现一个接口,这里的 ClassOrInterfaceName 是匿名内部类所继承的
类名或实现的接口名。但匿名内部类不能同时实现一个接口和继承一个类,也不能实现多个接口。如果
实现了一个接口,该类是 Object 类的直接子类,匿名类继承一个类或实现一个接口,不需要 extends
和 implements 关键字。②由于匿名内部类没有名称,所以类体中不能定义构造方法,由于不知道类名也不能使用关键字来创建
该类的实例。实际上匿名内部类的定义、构造、和第一次使用都发生在同样一个地方。此外,以下是一
个表达式,返回的是一个对象的引用,所以可以直接使用或将其复制给一个对象变量,如:TypeName obj=new Name(){ /*此处为类体*/ }
同样,也可以将构造的对象作为调用的参数。例:
someMethod(new Name(){ /*此处为类体*/ });
长达两周的面向对象终于结束了
无论你身处何时何地,永远不要放弃自己的梦想。即使你的梦想看起来很遥远,你仍然应该朝着它前进。因为只有在追求梦想的过程中,我们才能找到意义和价值。