7. JavaSE 面向对象
面向对象
初识面向对象
面向过程&面向对象
-
面向过程思想
- 步骤清晰简单,第一步做什么,第二步做什么
- 面向过程适合处理一些比较简单的问题
-
面向对象思想
- 物以类聚,分类的思维模式,思考问题的解决首先需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类 下的细节进行面向过程的思索。
- 面向对象适合处理复杂问题,适合处理需要多人协作的问题。
-
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
什么是面向对象
-
面向对象编程(Object-Oriented programming, OOP)
-
本质:以类的方式组织代码,以对象的组织(封装)数据。
-
抽象
-
三大特性:
- 封装
- 继承
- 多态
-
从认识论的角度考虑是先有对象后有类。对象是具体的事物,类是对象的抽象。
-
从代码运行角度考虑是先有类后有对象,类是对象的模板。
类与对象的关系
- 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但并不能代表某一个具体的事物。
- 动物、植物、手机、电脑…
- Person类、Pet类、Cat类等,都是用来描述/定义某一具体的事物应该具备的特点和行为。
- 对象是抽象概念的具体实例,如张三是人的一个具体实例、张三家里的狗旺财就是狗的一个具体实例。
创建与初始化对象
-
使用new关键字创建对象
-
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对
类中构造器的调用。 -
类中的构造器也被称为构造方法,创建对象时必须调用。有以下特点:
- 必须和类的名字相同
- 没有返回值,也不能写void
-
一个类即使什么都不写,也会存在一个默认的构造方法。
构造器
public class Person {
//一个类即使什么都不写,也会存在一个默认的无参构造方法
//显示地定义构造器
String name;
//作用:1. 使用new关键字,本质是在调用构造器
//2. 用来初始化对象的值
public Person(){} //无参构造
//有参构造 3.一旦定义了有参构造,无参就必须显示定义
public Person(String name){
this.name=name;
}
//Alt+insert 快捷键插入构造方法
}
内存分析
//定义一个宠物类
public class Pet {
public String name; //默认 null
public int age; //默认 0
//无参构造
public void shout(){
System.out.println("叫了一声");
}
}
//应用类,创建调用对象
public class Application {
public static void main(String[] args) {
Pet dog = new Pet();
dog.name = "旺财";
dog.age = 3;
dog.shout();
}
}
- 对象通过引用类型来操作:栈 - - ->堆
封装
-
该露的露,该藏的藏
- 程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外
部干涉;低耦合:仅暴露少量的方法给外部使用。
- 程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外
-
封装(数据的隐藏)
- 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
-
作用和意义
- 提高程序的安全性,保护数据。
- 隐藏代码的实现细节
- 统一用户的调用接口
- 提高系统的可维护性
- 便于调用者调用。
继承
-
继承的本质是对某一批类的抽象,从而实现对世界更好地建模。
-
extends的意思是”扩展“。子类是父类的扩展,使用关键字extends来表示。
-
Java中类只有单继承,没有多继承!一个类只能继承一个父类。
-
继承是类与类之间的一种关系,此外还有依赖、组合、聚合等。
-
继承关系的两个类,一个为子类(派生类),一个为父类(基类)子类继承父类。
-
子类和父类之间,从意义上讲应该具有”is a“的关系。
//学生类(子类)继承 人类(父类)
public class Student extends Person{ /*Person extends Object*/
...
}
super& this
- super
super访问或调用父类中的属性和方法。
//super调用父类中的属性
public class Person {
protected String name = "Dongyou";
}
public class student extends Person{
String name = panbin;
public void test(String name) {
System.out.println(name); //东友
System.out.println(this.name); //panbin
System.out.println(super.name); //Dongyou
}
}
//super调用父类中的方法
public class Person { //父类
public void print() {
System.out.println("Person");
}
}
public class student extends Person{ //子类
public void test1() {
super.print();
}
}
//调用父类构造器
public class Person{ }
public class Student extends Person{
//编译通过,子类构造器中会隐式的调用父类的无参构造器
//super();
public Student(){ }
}
super使用注意点:
1. super调用父类的构造方法,必须写在构造方法的第一行。
2. super必须只能出现在子类的方法或者构造方法中。
3. super 和 this 不能同时调用构造方法。
-
this
在子类中可以使用this来表示访问或调用子类中的属性或方法. -
super 与 this区别:
代表的对象不同:
this: 本身调用者这个对象。
super:代表父类对象。
前提:
this: 没有继承也可以使用。
super: 只能在继承条件才可以使用
构造方法
this():本类的构造
super():父类的构造
方法的重写
- 重写:需要有继承关系,子类重写父类的方法!
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大但不能缩小;Public > Protected > Default > Private
- 抛出的异常:范围可以被缩小,但不能扩大;ClassNotFoundException(小)---> Exception(大)
- 重写,子类的方法和父类必须一致;方法体不同!
- 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型
例如:父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子类型
A类继承B类
A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的是B类中的静态方法test
A a = new A();
a.test();//调用到的是A类中的静态方法test 可以看出静态方法的调用只和变量声明的类型相关 这个和非静态方法的重写之后的效果完全不同
- 私有方法不能被子类重写,子类继承父类后,是不能直接访问父类中的私有方法的,那么就更谈不上
重写了。
public class Person{ private void run(){} }
//编译通过,但这不是重写,只是俩个类中分别有自己的私有方法
public class Student extends Person{
private void run(){}
}
- 静态方法不能重写
- 父类的静态方法不能被子类重写为非静态方法 //编译出错
- 父类的非静态方法不能被子类重写为静态方法;//编译出错
- 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖)
- 为什么要重写:
- 父类的功能,子类不一定需要,或者不一定满足
- 重写父类方法的快捷键: Alt + Insert 选择 Override;
多态
-
多态编译:类型
-
即同一方法可以根据发送对象的不同而采用不同的行为方式
-
一个对象的实际类型是确定的,但可以指向对象的引用可以有很多
-
多态注意事项:
- 多态是方法的多态,属性没有多态
- 父类和子类,有联系 类型转换异常!
- 存在条件:继承关系,子类重写父类方法,父类引用指向子类对象! father f1 = new Son();
instanceof和类型转换
- instanceof 引用类型比较,判断一个对象是什么类型
// Object > String
// Objest > Person > Student
// Objest > Person > Teacher
Object object = new Student();
// X instanceof Y,X引用指向的对象是不是Y的子类
System.out.println(object instanceof Student); //true
System.out.println(object instanceof Person); //true
System.out.println(object instanceof Teacher); //false
System.out.println(object instanceof Object); //true
System.out.println(object instanceof String); //false
//类型之间的转化:父-子(高-低),低可以转换为高
Person obj = new Syudent(); //只能用Person方法(重写了用子类重写过的方法)
(Syudent)obj.go(); //强转之后可以用Student方法(Student->go())
-
类型转换
- 父类引用指向子类的对象
- 把子类转换为父类,向上转型,会丢失自己原来的一些方法
- 把父类转换为子类,向下转型,强制转换,才调用子类方法
- 方便方法的调用(转型),减少重复的代码,简洁。
修饰符
static(静态)
1、static变量
- 在类中,使用static修饰的成员变量,就是静态变量,也称为类变量,反之为非静态变量。
private static int age; //静态变量
private double score; //非静态变量
- 静态变量与非静态变量的区别
- 静态变量属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.
public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student();
System.out.println(Student.age); //静态访问,推荐使用类名访问静态成员
System.out.println(s.age);
System.out.println(s.score); //非静态访问,要先new对象再访问
}
}
* 静态变量对于类而言在内存中只有一个,能被类的所有实例所共享。
* 实例变量对于类的每个实例都有一份,它们之间互不影响.
* 在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存,所以静态变量可以使用类名来
直接访问,而不需要使用对象来访问.
2、static方法
- 在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。
- 静态方法和非静态方法的区别
- 方法的调用方式不同
public class Student{
public static void run(){};
public void go(){};
}
poublic void test(){
Student.run(); //类.方法()
Student s = new Student();
s.go(); //对象.方法();
}
- 父类的静态方法可以被子类继承,但是不能被子类重写
- 父类的非静态方法不能被子类重写为静态方法 ;
- this和super在类中属于非静态的变量.(静态方法中不能使用)
3、代码块和静态代码块
【类中可以编写代码块和静态代码块】
public class Person {
{
//代码块(匿名代码块)
}
static{
//静态代码块
}
}
-
匿名代码块和静态代码块的执行
- 因为没有名字,程序不能主动调用这些代码块。
- 匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动行.
- 静态代码块是在类加载完成之后就自动执行,并且只执行一次.
-
匿名代码块和静态代码块的作用
- 匿名代码块的作用是给对象的成员变量初始化赋值
- 静态代码块的作用是给类中的静态成员变量初始化赋值。
4、创建和初始化对象的过程
Student s = new Student();
- 类加载,同时初始化类中静态的属性
- 执行静态代码块
- 分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值(如果有的话) 6. 执行匿名代码块
- 执行构造器
- 返回内存地址
finnal
- 用final修饰的类不能被继承,没有子类。
- 用final修饰的方法可以被继承,但是不能被子类的重写。
- 用final修饰的变量表示常量,只能被赋一次值.其实使用final修饰的变量也就成了常量了,因为值不会再变
了。
abstract
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那
么该类就是抽象类。
1、抽象类和抽象方法的关系
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
2、语法
/*
声明类的同时,加上abstract修饰符就是抽象类
声明方法的时候,加上abstract修饰符,并且去掉方法的大括号,同时结尾加上分号,该方法就是抽象方法。
*/
//抽象类
public abstract class Action{
//抽象方法
public abstract void doSomething();
}
//子类实现父类中定义的抽象方法
public void doSomething(){...}
3、抽象类 vs 抽象方法
- 抽象类中可以没有抽象方法,但有抽象方法的类一定要声明为抽象类。
- 抽象类不能使用new来创建对象,它是用来让子类继承的。
- 抽象方法只有方法的声明,没有实现,让其子类实现。
- 子类继承抽象类,必须实现抽象类的所有方法,否则该子类也要声明为抽象类。
接口(interface)
1、接口的本质
- 普通类:只有具体实现
- 抽象类:具体实现和规范(抽象方法)都有!
- 接口:只有规范!
2、接口与抽象类的区别
- 声明类的关键字是class,声明接口的关键字是interface。
- 继承的关键字是extends
- 实现的关键字是implements
- 一个类可以实现多个接口
- 一个子类只能继承一个抽象类
3、接口中的方法都是抽象方法
//interface接口,接口都要有继承类
//实现类(implements 可以继承多个接口)
//多继承,利用接口实现多继承
public interface UserService {
//接口中所有定义的就去其实都是抽象的 默认修饰 public abstract
void add();
void delete();
void update();
void query();
}
4、接口中的变量都是静态常量(public static final
接口中可以不写任何属性,但如果写属性了,该属性必须是public static final修饰的静态常量。
public interface UserService {
//常量- 默认修饰 public static final
int AGE = 99;
}
5、一个类可以实现多个接口
public class Student implements A,B,C,D{
//Student需要实现接口A B C D中所有的抽象方法
//否则Student类就要声明为抽象类,因为有抽象方法没实现
}
6、一个接口可以继承多个父接口
public interface A {
public void testA();
}
public interface B {
public void testB();
}
public interface C extends A,B {
public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法
// Student的对象也就同时属于A类型 B类型 C类型
public class Student implements C{
public viod testA(){}
public viod testB(){}
public viod testC(){}
}
7、接口的作用
接口的最主要的作用是达到统一访问,就是在创建对象的时候用接口创建
【接口名】 【对象名】= new 【实现接口的类】
这样你想用哪个类的对象就可以new哪个对象了,不需要改原来的代码。
//定义动物接口
public interface Animal {
void eat();
}
//熊猫类实现了动物接口
public class Panda implements Animal{
@Override
public void eat() {
System.out.println("熊猫吃竹子!");
}
}
//羊类实现了动物接口
public class Sheep implements Animal{
@Override
public void eat() {
System.out.println("羊儿吃草!");
}
}
main:
Animal panda = new Panda();
Animal sheep = new Sheep();
panda.eat(); //熊猫吃竹子!
sheep.eat(); //羊儿吃草!
内部类
内部类概述
内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内
部类,而A类相对B类来说就是外部类了。
内部类不是在一个java源文件中编写俩个平行的俩个类,而是在一个类的内部再定义另外一个类。 我们
可以把外边的类称为外部类,在其内部编写的类称为内部类。
内部类分为四种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类(实例内部类、非静态内部类)
public class Outer {
private int id=10;
public void out(){
System.out.println("这是外部类的方法");
}
//内部类
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
}
}
public class Test{
public static void main(String[] args) {
//实例化成员内部类分两步
//1、实例化外部类
Outer outObject = new Outer();
//2、通过外部类调用内部类
Outer.Inner inObject = outObject.new Inner();
//测试,调用内部类中的方法
inObject.in();//打印:这是内部类方法
}
}
静态内部类
-
使用static修饰的内部类就叫静态内部类。
-
static一般只修饰变量和方法,平常不可以修饰类,但是内部类却可以被static修饰。
-
注意 :
- 我们上面说的内部类能够调用外部类的方法和属性,在静态内部类中就行了,因为静态内部类没有了指向外部类对象的引用。除非外部类中的方法或者属性也是静态的。这就回归到了static关键字的用 法。
-
- 2、静态内部类能够直接被外部类给实例化,不需要使用外部类对象
-
Outer.Inner inner = new Outer.Inner();
-
- 3、静态内部类中可以声明静态方法和静态变量,但是非静态内部类中就不可以声明静态方法和静态变
-
- 量
局部内部类
public class Outer {
private int id; //在method01方法中有一个Inner内部类,这个内部类就称为局部内部类
public void method01(){
class Inner{
public void in(){
System.out.println("这是局部内部类");
}
}
}
}
- 局部内部类是在一个方法内部声明的一个类
- 局部内部类中可以访问外部类的成员变量及方法
- 局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
- 注意:
- 在局部内部类中,如果要访问局部变量,那么该局部变量要用final修饰
- 局部内部类不能通过外部类对象直接实例化,而是在方法中实例化出自己来,然后通过内部类对象
调用自己类中的方法。看下面例子就知道如何用了。
匿名内部类
-
如果一个对象只要使用一次,那么我们只需要new Object().method()。不需要给这个实例保存到该类型变量中去。这就是匿名对象。
-
匿名内部类需要依托于其他类或者接口来创建
- 如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类
- 如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
-
匿名内部类的声明必须是在使用new关键字的时候,
- 匿名内部类的声明及创建对象必须一气呵成,并且之后能反复使用,因为没有名字。
//A是一个类(普通类、抽象类都可以),依托于A类创建一个匿名内部类对象
A a = new A(){
//实现A中的抽象方法
//或者重写A中的普通方法
};
//1. 匿名内部类除了依托的类或接口之外,不能指定继承或者实现其他类或接口,同时也不能被其他类所
//继承,因为没有名字。
//2. 匿名内部中,我们不能写出其构造器,因为没有名字。
//3. 匿名内部中,除了重写上面的方法外,一般不会再写其他独有的方法,因为从外部不能直接调用到。(间
接是调用到的)
- 使用匿名内部类
public class Test {
public static void main(String[] args) {
//如果我们需要使用接口中的方法,我们只需要走一步,就是使用匿名内部类,直接将其 类的对象创建出来。
new Test1(){
public void method(){
System.out.println("实现了Test接口的方法");
}
}.method();
}
}
interface Test1{
public void method();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?