文章目录
1 面向过程&面向对象
- 面向过程思想(线性思维)
- 步骤清晰简单,第一步做什么,第二步做什么
- 面向过程适合处理一些较为简单的问题
- 面向对象思想
- 物以类聚,分类的思维模式,解决问题首先会思考问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
- 面向对象时候处理复杂的问题,适合处理需要多人协作的问题
- 对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理
2 什么是面向对象
- Object-Oriented Programming,OOP
- 面向对象编程的本质就是:以类的方式组织代码,以对象的形式组织(封装)数据
- 抽象:抽取关键相关特性(属性和方法)构成对象,用程序的方法逻辑和数据结构属性模拟现实的世界对象。
- 三大特性:封装、继承、多态
- 从认识论角度考虑是先有对象后有类。对象是具体的事物;类是抽象的,是对对象的抽象
- 从代码运行角度考虑是现有类后有对象。类是对象的模板
3 类和对象的关系
- 类是一种抽象的数据类型,它是对某一类事物的整体描述定义,但不能代表某一个具体的事物
- 动物、植物、手机、电脑等等
- 这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为
- 对象是抽象概念的具体实例
- 张三就是人的一个具体实例,张三家里的大黄就是狗的一个具体实例
- 对象能够体现出特点,展现出功能,不是一个抽象的概念
4 创建与初始化对象
- 使用
new
关键字创建对象 - 使用
new
关键字创建时,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对调用类中构造器 - 类中的构造器也称为构造方法,是在创建对象时必须调用的,且有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写
void
5 构造器详解
-
构造器用于初始化对象的值
-
一个类即使什么也不写,也会默认存在一个空的无参构造方法
-
除了默认的无参构造器,也可以显式地定义无参构造器
class Person { String name; // 显式地定义一个无参构造器 public Person() { this.name = "Kayne"; } } public class Application { public static void main(String[] args) { // 使用new关键字,本质是在调用构造器 Person person = new Person(); System.out.println(person.name); // Kayne } }
-
构造方法可以有参数,形成有参构造器。一旦定义了有参构造器,默认的空无参构造器就不会生效,如果还想使用无参构造,就必须显式定义无参构造器
class Person { String name; // 定义一个有参构造器 public Person(String name) { this.name = name; } // 需要显式定义一个无参构造器 public Person() {} } public class Application { public static void main(String[] args) { // 使用new关键字,本质是在调用构造器 Person p1 = new Person("Alice"); System.out.println(p1.name); // Alice // 如果不显式定义无参构造器,就不能以下面这种方式创建Person对象 Person p2 = new Person(); System.out.println(p2.name); // null } }
-
在IDEA中,使用alt+insert快捷键,选择Constructor可以快速生成需要的构造器
6 创建对象内存分析
对于如下程序
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age
}
public Person() {}
public void run() {
System.out.println(name + "在跑");
}
}
public class Application {
public static void main(String[] args) {
Person p1 = new Person("Alice", 18);
p1.run();
Person p2 = new Person();
p2.name = "Bob";
p2.age = 20;
p2.run();
}
}
此时内存如下:
7 封装
-
程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节由自身完成,不允许外部干涉;低耦合指一个类仅暴露少量的方法给外部使用
-
封装就是数据的隐藏,通常应该禁止直接访问一个对象中数据,而是通过接口来访问
-
简单来说,类的属性需要定义为私有,通过get和set方法访问
class Student { // 属性私有 private String name; private int id; // 提供可以操作这个属性的方法 // public的set、get方法 // get用于获取这个数据 public String getName() { return this.name; } public int getId() { return this.id; } // set用于为属性设置值 public void setName(String name) { this.name = name; } public void setId(int id) { this.id = id; } }
-
使用封装可以提高程序的安全性、保护数据,并隐藏代码的实现细节
-
封装可以统一接口,增加了系统的可维护性
8 访问修饰符
-
public
:用public
修饰的类、类属变量及方法,包内及包外的任何类(包括子类和普通类)均可以访问 -
private
:用private
修饰的类、类属变量及方法,只有本类可以访问,而包内包外的任何类均不能访问它,子类也不能访问父类的私有属性和方法 -
default
:用default
修饰的类、类属变量及方法,只有本包中的类和本类可以访问,而其他的包和其他包中的类均不能访问它 -
protected
:用protected
修饰的类、类属变量及方法,同一个包中的所有类都可以访问;其他包中,只有子类可以访问 -
下表为Java访问控制符的含义和使用情况
类内 本包 外部包子类 外部包 public
√ √ √ √ protected
√ √ √ × default
√ √ × × private
√ × × ×
9 继承
-
Java中类只有单继承,没有多继承
-
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
-
继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字
extends
表示class Person {} // Female类继承于Person类,即Female是Person类的一个子类 class Female extends Person {}
-
子类和父类之间,从意义上讲具有is-a的关系,上面的代码可以看做
Female
isPerson
-
子类继承了父类就获得了父类的属性和方法(非私有),子类是父类的扩展
class Person { public int money = 100000000; private int age = 40; public void say() {} } // Student类中没有定义任何方法和属性 class Student extends Person {} public class Application { Student student = new Student(); // Student类中有从Person类中继承来的say()方法和money属性 student.say(); System.out.println(student.money); // 会输出100000000 System.out.println(student.age); // 子类无法访问父类私有属性和方法 }
-
Java中,所有的类都默认直接或间接继承
Object
类
10 this
关键字
-
this
是自身的一个对象,代表对象本身,可以理解为指向对象本身的一个指针 -
当局部变量与类成员变量重名时,在方法中使用
this
表示成员变量以示区分;如果在一个方法中没有局部变量与成员变量同名,那么在这个方法中使用成员变量也不必使用this
class Demo { String str = "成员变量"; void func(String str) { System.out.println(str); System.out.println(this.str); this.str = str; System.out.println(this.str); } } public class This { public static void main(String[] args) { Demo demo = new Demo(); demo.func("局部变量"); /* * 输出结果: * ======== * 局部变量 * 成员变量 * 局部变量 */ } }
-
使用
this
关键字将当前对象传递给其他方法class Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); } } class Peeler { static Apple peel(Apple apple) { // 去皮操作 return apple; } } class Apple { // 调用getPeeled方法返回一个去皮后的苹果 // 这里需要传入一个没有去皮的苹果作为参数给peel方法 Apple getPeeled() { // this表示没有去皮的苹果,即Apple这个类本身 return Peeler.peel(this); } } public class This { public static void main(String args[]) { new Person().eat(new Apple()); } }
-
当需要返回当前对象的引用时,可以在方法中写
return this
,此时返回经过修改后的对象,因此很容易对同一个对象进行多次操作(链式调用)public class This { int num = 0; This increment() { num += 2; return this; } void print() { System.out.println("num = " + num); } public static void main(String[] args) { This t = new This(); t.increment().increment().print(); // 结果为4 } }
-
在构造函数中调用其他构造函数可以使用
this()
public class Person { String name = "default"; int age = 0; Person(int _age) { age = _age; System.out.println("只有int类型参数的构造函数"); } Person(String _name) { name = _name; System.out.println("只有String类型参数的构造函数"); } Person(int _age, String _name) { this(_name); // this(_name); 不可调用 age = _age; System.out.println("有String和int类型参数的构造函数"); } Person() { this("Alice", 22); System.out.println("默认构造函数"); } void print() { // this(); 不可调用 System.out.println("name = " + name + " age = " + age); } public static void main(String[] args) { Person person = new Person(); person.print(); } }
-
this
调用构造函数时需要注意:this
只能调用一个构造函数,在一个构造函数中不能同时调用多个构造函数this
调用的构造函数必须位于最开始的位置,这也说明了为什么同一个构造函数中不能调用多个构造函数,因为从第二个开始的构造函数必然不在最开始位置- 在构造函数之外的方法中不能使用
this
调用构造函数
-
this
不能用在static
方法中,因为this
指代当前对象,而static
没有实例对象
11 super
关键字
-
super
只能出现在子类的方法或构造方法中 -
通过
super
可以在子类中访问父类的属性class Father { protected String name = "father"; } class Son extends Father { private String name = "son"; public void print() { System.out.println(name); // son System.out.println(super.name); // father } }
-
通过
super
可以在子类中使用父类的方法class Father { public void print() { System.out.println("print() in Father"); } } class Son extends Father { public void print() { System.out.println("print() in Son"); } public void test() { this.print(); // 子类方法 super.print(); // 父类方法 } }
-
父类中通过
private
修饰的属性和方法无法通过super
关键字来访问 -
通过
super()
可以在子类的构造函数中调用父类的构造函数class Father { public Father() { System.out.println("Father无参构造器"); } } class Son extends Father { public Son() { super(); System.out.println("Son无参构造器"); } } public class Super { public static void main(String[] args) { Son son = new Son(); /* * 输出结果: * ================= * Father无参构造器 * Son无参构造器 */ } }
-
子类的构造函数中默认第一行为
super()
,只是没有显式调用 -
和
this
类似,如果显式调用父类构造器,那么super()
必须在位于子类构造器的第一行 -
this()
和super()
不能同时显式出现
12 方法的重写(override)
-
由于父类的功能子类不一定需要,或者不满足子类的需求,所以子类需要重写父类的方法
-
静态方法不能被重写,子类的静态方法不会替换父类中的静态方法
class B { public static void test() { System.out.println("B->test()")'' } } class A extends B { // 不构成重写 public static void test() { System.out.println("A->test()")'' } } public class Application { public static void main(String[] args) { // 此时方法的调用只和左边定义的数据类型有关 A a = new A(); a.test(); // A->test(); // 父类的引用指向子类 B b = new A(); b.test(); // B->test(); } }
-
只能重写非静态方法
class B { public void test() { System.out.println("B->test()")'' } } class A extends B { @Override public void test() { System.out.println("A->test()")'' } } public class Application { public static void main(String[] args) { A a = new A(); a.test(); // A->test(); // 父类的引用指向子类 B b = new A(); b.test(); // A->test(); } }
-
静态方法属于类,非静态方法属于对象
-
在没有
static
时,此时调用的是对象的方法,而b
是A
类new
出来的对象,因此调用的是A
类的方法 -
在有
static
时,此时调用的是类的方法,b
是用B
类定义的,因此调用的是B
类的方法
-
-
重写的规则:
- 方法名必须相同
- 参数列表必须相同
- 返回值类型必须相同
- 修饰符范围可以扩大,不可以缩小(从小到大:
private
→protected
→default
→public
) - 抛出的异常范围可以缩小,不可以扩大
-
用
final
修饰的方法不能被重写,同时private
方法也不能被重写
13 多态
-
多态指同一方法可以根据发送对象的不同而采用多种不同的行为方式
-
一个对象的实际类型是确定的,但对象可以指向的引用类型不能确定
// 指向的引用类型不确定 // 父类的引用可以指向子类 Female obj1 = new Female(); Person obj2 = new Female(); Object obj3 = new Female();
-
多态存在的条件
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象
-
多态只针对方法,属性没有多态性
-
对象能执行哪些方法,主要看对象左边的类型
class Person { public void run() { System.out.println("run"); } } class Female extends Person { @Override public void run() { System.out.println("run in Female"); } public void eat() { System.out.println("eat"); } } public class Application { public static void main(String[] args) { Female p1 = new Female(); Person p2 = new Female(); // 父类虽然指向子类,但不能调用子类独有的方法 p1.run(); // run in Female p2.run(); // 子类重写了父类的方法后,执行的是重写后的方法,为run in Female p1.eat(); // eat // p2.eat(); p2没有eat()这个方法,无法调用 ((Female) p2).eat(); // 可以通过强制类型转换使用子类的方法 } }
14 instanceof
-
instanceof
是Java中的一个双目运算符,用于测试一个对象是否为一个类的实例,其中obj
是一个对象,Class
是一个类或一个接口boolean result = obj instanceof Class;
-
编译器会检查
obj
是否能转换为右边的Class
类型,如果不能转换则会直接报错;如果不能确定类型,则编译通过,运行时再处理 -
obj
必须为引用类型,不能是基本类型int i = 0; System.out.println(i instanceof Integer); // 编译不通过 System.out.println(i instanceof Object); // 编译不通过
-
obj
为null
时,结果总是false -
当
obj
是Class
的实例对象,返回trueInteger i = new Integer(1); System.out.println(i instanceof Integer); // true
-
当
obj
是Class
的直接或间接子类,返回true,否则返回falseclass Person {} class Man extends Person {} public class Application { Person p1 = new Person(); Person p2 = new Man(); Man man = new Man(); System.out.println(p1 instanceof Man); // false System.out.println(p2 instanceof Man); // true System.out.println(man instanceof Man); // true }
-
当
obj
是Class
接口的实现类,返回truepublic class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class Application { ArrayList arrayList = new ArrayList(); List list = new ArrayList(); System.out.println(arrayList instanceof List); // true System.out.println(list instanceof List); // true }
15 类型转换
-
父类如果想要使用子类的方法,可以向下强制转型
class Person {} class Female extends Person { public void eat() { System.out.println("eat"); } } public class Application { public static void main(String[] args) { Person p = new Female(); // 向下强制转换后就可以使用子类的eat()方法了 ((Female) p).eat(); } }
-
子类转换为父类可以自动转换,但可能会丢失自己本身的一些方法
Female female = new Female(); Person person = female; female.eat() // 向上转型后不能调用eat()方法了
16 static
关键字
-
使用
static
修饰的变量和方法可以通过类名直接调用,无需实例化对象 -
在类中可以通过一组大括号
{}
定义代码块,其中添加了static
的代码块称为静态代码块,没有static
的代码块称为匿名代码块。代码块在构造方法之前执行,静态代码块只在类加载时执行一次public class Application { { System.out.println("匿名代码块"); } // 只执行一次 static { System.out.println("静态代码块"); } public Application() { System.out.println("构造方法"); } public static void main(String[] args) { Application app1 = new Application(); Application app2 = new Application(); /* * 输出结果: * ========= * 静态代码块 * 匿名代码块 * 构造方法 * 匿名代码块 * 构造方法 */ } }
-
通过
static
可以静态导入包(不常用)// 静态导入包 import static java.lang.Math.random; import static java.lang.Math.PI; public class Test { public static void main(String[] args) { // 可以直接调用 System.out.println(random()); System.out.println(PI); } }
17 抽象类
-
abstract
修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类// 抽象类 public abstract class Action { // 抽象方法,没有方法的实现 public abstract void doSomething(); // 抽象类中可以有普通方法 public void run() { System.out.println("run..."); } }
-
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
-
抽象类中可以有普通方法
-
抽象类不能使用
new
关键字来创建对象,只能由子类继承 -
抽象方法只有方法的声明,没有方法的实现,是用来让子类实现的
public class A extends Action { @Override public void doSomething() { // 具体实现 } }
-
子类继承抽象类,就必须要实现抽象类中的抽象方法,否则该子类也必须声明为抽象类
-
虽然抽象类不能被实例化,但可以有构造函数。由于抽象类的构造函数在实例化派生类之前发生,所以,可以在这个阶段初始化抽象类字段或执行其它与子类相关的代码。
18 接口
-
声明接口的关键字是
interface
,声明类的关键字是class
-
接口中不能有具体的方法实现
public interface UserService { void add(String name); // 不能写实现,否则会报错 void delete(String name); }
-
接口中的所有方法定义默认为
public abstract
-
接口需要有其实现类,使用
implements
关键字,实现类必须重写接口中的所有方法// 标准写法 public class UserServiceImpl implements UserService { @Override void add(String name) {} @Override void delete(String name) {} }
-
Java中类只能单继承,但是可以实现多个接口
// 可以通过implements关键字实现多个不同接口 public class UserServiceImpl implements UserService, TimeService { @Override // 重写多个接口中的所有方法 }
-
接口中定义的变量都是常量,默认用
public static final
修饰 -
接口不能被实例化,且接口中没有构造方法
-
接口就是规范,定义的是一组规则,体现了显示世界中“如果你是…则必须能…”的思想
-
接口可以达到约束和实现分离的功能
-
接口的本质是契约,制定好后大家都要遵守
-
面向对象(OO)的精髓是对对象的抽象,其中接口最能体现这一点
19 内部类
-
内部类就是在一个类的内部再定义一个类
-
成员内部类,内部类可以获得外部类的私有属性
class Outer { private id = 2333; public void out() { System.out.println("这是外部类的方法"); } public class Inner { public void in() { System.out.println("这是内部类的方法"); } // 获得外部类的私有属性 public void getID() { System.out.println(id); } } } public class Application { public static void main(String[] args) { Outer outer = new Outer(); // 通过外部类类实例化内部类 Outer.Inner inner = outer.new Inner(); inner.in(); inner.getID(); } }
-
静态内部类,内部类用
static
修饰,此时不能访问外部类的非静态属性class Outer { private id = 2333; public static class Inner { public void getID() { System.out.println(id); // 编译不通过,无法获得外部类的非静态属性 } } }
-
局部内部类,内部类还可以定义在方法中
class Outer { public void method() { class Inner { public void in() { System.out.println("这是内部类的方法"); } } } }
-
匿名内部类,实例化对象是不定义对象的名称,这样就不用将实例保存在变量中。同时可以在匿名内部类中重写类的方法,或者实现接口
class Animals { public void eat() { System.out.println("动物要吃东西"); } } public class Outer { public static void main(String[] args) { new Animals().eat(); // 输出:动物要吃东西 // 也可以通过匿名内部类重写eat()方法 new Animals() { @Override public void eat() { System.out.println("这是匿名内部类"); } }.eat(); // 输出:这是匿名内部类 } }