思维导图:
目录
3.关联(Association)与聚合(Aggregation)
2.try{可疑代码}catch(Exception){//对异常进行处理}
一:OOP解决问题的过程
1.思考解决该问题所需哪些对象
2.思考这些对象所需的属性,方法
3.思考个对象之间的关系, 对象之间如何交互
二:OOP的三大特性
1.封装
第一层含义:将对象的属性和行为看成一个密不可分的整体 封装在一个对象(类)中
第二层含义:将对象中的属性(不需要被外界知道的)隐藏起来 不让外界轻易获取 或者某些方法只需要外界使用其功能不需要外界知道方法的具体细节 需要用修饰符进行修饰 比如:将类中的某些元素用private修饰符修饰 如此在此类外则无法获取该属性的内容,而用非private修饰的元素 可以用类名.属性进行获取和赋值
封装的好处:
1.将对象的属性方法隐藏起来 不会轻易被外界改变 使得代码更加安全 外界只允许用规定的方法访问
2.将对象不需要被外界知道的细节隐藏提来
3.封装后的类修改其方法更便捷 方便向其中加入控制语句
2.继承
继承的好处:
1.可以重用父类的代码
2.子类可以自由拓展父类的代码
继承的关键字:extends
继承的特点:
1.子类既可以重用父类的方法(避免重复写) 也可以重写父类方法(灵活变化) 还可以拥有自己的属性,方法 (拓展了程序的功能)
2.单向继承 一个子类类只能有一个父类 并且父类不能访问子类的属性和方法
3.Java的单根继承 所有类都继承自Object类
3.多态
实现方式:继承 子类继承父类 重写 新增属性方法
接口 不同的类对同一接口的抽象方法进行不同的重写
类型转换:向上转型 小转大 子类转父类 自动类型转换
向下转型 大转小 父类转子类 需要强制类型转换
三、类和对象
类:类是封装的位置
类的外壳: 访问修饰符 关键词class 类的名称
类的内部: 成员属性 方法
方法分为: 构造方法Constructor 自定义方法 入口方法main方法
访问修饰符:private protected 默认 public
类作为对象的类型
对象是类的一个具体实例 如果直接输出对象 默认调用对象的toString方法
声明对象和创建对象的区别:
声明对象:创建了一个类的实例 例:Person p;
创建对象:为这个对象分配一块内存空间 存储该数据的类型和方法
内部类和匿名内部类
public class TestClass{
//内部类
class test{}
//匿名内部类 在一个类中new了一个未命名的类
public Fuction<Integer,String> get(){
return new Fuction(Integer,String){
@Override
public String apply(Integer integer){
return String.valueOf(integer);
}
};
}
}
四、方法
1 .实例方法
构造方法:
用于创建对象时进行初始化
没有返回值,并且在创建对象时会被自动调用
用关键字"new"来创建对象时,系统会自动调用对应的构造方法
必须通过创建类的实例来调用,可以访问类的非静态成员变量和方法
2. 静态方法:
可以再不实例化类的情况下被外部调用,用类名.方法名的方式调用
属于类的方法,不能访问非静态成员变量和非静态方法,只能访问静态成员变量和静态方法
使用关键字"static"来声明
无法访问类的实例变量和实例方法,因为在调用静态方法时可能还没有创建任何该类的实例
通常用于定义一些通用的操作或提供一些便利的方法
静态方法不能被子类覆盖,因为子类无法继承静态方法
3. 抽象方法
没有具体实现代码,只有方法声明,需要在子类中被覆盖实现
使用关键字"abstract"来声明
如果一个类包含抽象方法,那么它必须声明为抽象类,不能直接创建该类的实例
不能使用private或final修饰符,因为这些修饰符与抽象方法的概念不兼容
抽象方法必须在包含它的抽象类或接口中声明,不能直接创建抽象方法的实例
4. 重载方法
在同一个类中,有相同的方法名,但是参数列表不同或者参数类型不同
5. 重写方法
子类中定义了与父类中同名、同参数列表、同返回值类型的方法,子类继承了父类的方法,但是重新实现了该方法的具体实现
重写方法必须具有相同的方法签名(即方法名、参数列表和返回值类型),并且访问修饰符不能更严格
运行时根据对象类型来确定具体调用哪个方法,而不是根据引用类型来决定
五、接口implement
Java利用接口实现多根
1.创建 public interface 接口名
2.实现 类名 implement 接口名
3. 接口中的属性 默认是 public static final的
4.接口中的方法一般是抽象的(不具有方法体)
使用关键词default可以定义默认方法 public default void defaultMethod(){方法体}
default方法可以被实现该接口的类直接调用 也可以被重写
六、函数式接口
只包含一个抽象方法的接口 可以使用 lambda 表达式和方法引用来创建函数式接口的实例
//自定义式函数式接口
//首先,声明一个Java接口,并使用@FunctionalInterface注解来确保它是函数式接口。
这个函数式接口包含了一个泛型类型T和R,表示输入参数和返回值类型。
@FunctionalInterface
public interface MyFunction<T,R>{
public R apply(T t)
}
//然后,定义一个方法,该方法接受函数式接口作为参数,并在该方法中调用此函数
//此方法将第一个参数t和一个函数式接口作为参数。在方法体内,我们通过调用函数式接口中定义的apply()方法来使用传递的函数
//此方法将第一个参数t和一个函数式接口作为参数。在方法体内,我们通过调用函数式接口中定义的apply()方法来使用传递的函数
public static <T,R> void MyMethod(T t ,MyFuction<T,R> fuction){
R result = fuction.apply(t);
System.out.println(result);
}
//最后,创建一个Lambda表达式并传递给myMethod()方法
public static void main(String args[]){
MyMethod("Hello World",s->s.length());
}
//输出结果:11
//"Hello World"对应 t参数 lambda表达式s->s.length()对应function 此处s指t参数 s.length()指返回值R result
JDK内置函数式接口
//一入一出
interface Function<T,R>{
R apply(T t);
}
//只入不出
interface Consumer<T>{
void accept(T t);
}
//只出不入
interface Supplier<T>{
T get();
}
//入参出布尔
interface Predicate<T>{
boolean test(T t);
}
面向接口编程:编程过程中优先考虑接口 而不考虑具体实现
七、OOP的编程原则
1.单一职能原则:
一个接口或类只应有一个职能 只负责一件事
2.依赖倒置原则:
高层模块不应依赖于底层模块,而应该依赖于抽象接口
通常情况下,底层代码注重于具体实现,更细节化;而高层模块更注重于整体的功能和业务逻辑
通过引入一个抽象层面作为中间层,高层模块可以将其依赖转移至抽象层,而不用依赖于底层模块,降低了代码的耦合度。
3.接口隔离原则:
接口应设计的尽可能精简,避免设计过于复杂的接口
4.开放封闭原则:
系统应面向拓展开放,面向修改封闭
5.里氏替换原则:
一方面,子类应该完全实现父类的方法,就是说在继承时,子类应该完全覆盖或实现父类的所有方法,做到可以替代父类而不影响程序正确性。
另一方面,子类可以有自己的个性化的行为,就是说在继承时,子类可以改变父类方法的实现方式,但不能改变方法的预期结果。
八、泛型
1.类级泛型
将类型参数化应用于整个类
class Container<T,U...>{...}
未来将所需要的参数类型传入<>中
2.方法级泛型
将类型参数化应用于某个特定的方法
<T,U...> U method(T t,..){....}
参数是T t ... 返回类型是U
3.泛型静态方法
可以在不实例化类的情况下,使用泛型参数的静态方法
class myClass{
public static <T> void method(T t){方法体...}
}
//调用:
class someClass{
myClass.method(123); //调用带有Integer类型参数的方法
myClass.method("Hello");//调用带有String类型参数的方法
}
<T>表示这是一个泛型方法
使用泛型静态方法时,可以根据需求提供对应的实际类型参数
4.泛型的边界
<? extends T> //定义了泛型的上边界 泛型可以是T或者T的子类
<? super T> //定义了泛型的下边界 泛型可以是T或者T的父类
九、类类关系
1.继承(Inheritance)
子父类通过extends关键词继承,一个类通过继承另一个类的属性与方法,从而创建出一个子类
2.实现(Implement)
一个类实现某接口(通过implement关键词)或者抽象类(通过extends关键词)时,称这两者为实现关系
3.关联(Association)与聚合(Aggregation)
关联:一个类中包含另一个类的对象作为他的成员变量,两者之间存在交互关系,但是生命周期相互独立
例如:学校与学生 订单与顾客
聚合:是一种特殊的关联关系,表示整体对象(容器)包含部分对象,并且部分对象的生命周期可以独立于整体对象存在
聚合是一种强关联关系,关联表示类与类之间的交互关系,聚合表示整体对象拥有部分对象的关系,两者中的对象都拥有各自独立的生命周期
public class Order{
private Customer customer;// 体现关联关系
public Order(Customer customer){
this.customer = customer;
}
}
public class Customer{
private List<Order> orders;// 体现了聚合关系
public Customer(){
this.orders = new ArraysList<>)();
}
public addOrder(Order order){
orders.add(order);
}
}
自关联:
指一个类中的某个字段类型是这个类本身。这种关系常见于树形结构,链表等数据结构。
public class TreeNode{
private int value;
private TreeNode leftNode;
private TreeNode rightNode;
public TreeNode(int value){
this.value = value;
}
//自关联的体现
public TreeNode getLeftNode(){
return leftNode;
}
public void setLeftNode(TreeNode leftNode){
this.leftNode = leftNode;
}
public TreeNode getRightNode(){
return rightNode;
}
public void setRightNode(TreeNode rihgtNoe){
this.rightNode = tightNode;
}
上述代码中,一个TreeNode包含了value和leftNode,rightNode 左右节点均是TreeNode类型的,形成了自关联关系
4.组合(Composition)
组合也是一种整体与部分之间的关系,但是与聚合关系不同的是,组合关系中的整体与部分之间的关系是很强的,部分不能脱离于整体独自存在,是一种强聚合的关系,整体的生命周期结束也就意味着部分的生命周期结束
例如:人体与大脑
//外部类:人体
public class HumanBody{
private Brain brain;//人体包含一个Brain对象
public HumanBody(Brain brain){
this.brain = brain;//在创建人体对象的同时实例化大脑对象
}
public void HumanThink(){
brain.think();//在人体中调用大脑的think方法进行思考
}
}
//内部类:大脑
public class Brain{
private String thoughts;
public void think(){
//思考逻辑
}
}
5.少用继承,多用组合
继承是一种 is-a 的关系 理解为:是一个。。
具备多态
可以实现向上转型
组合是一种 has-a 的关系,理解为: 有一个。。
使用组合的时机
一个类需要访问另一个类的属性和方法
一个类依赖于另一个类的功能来实现自己的某些操作
一个类由多个其他类的对象组成,组成了更复杂的对象结构
当使用组合而不是继承时,可以更好地实现代码的复用和灵活性。以下是一些例子,说明了为什么组合优于继承:
-
车辆类的设计:假设我们正在设计一个车辆类系统。如果使用继承,可能会创建一个基础的"Vehicle"类,并派生出不同类型的车辆,如"Car"、"Truck"和"Motorcycle"等。然而,这种继承关系可能在遇到特殊情况时变得复杂,例如混合型车辆(既有汽车又有摩托车功能)或需要动态改变车辆类型的情况。相反,通过使用组合,可以将车辆的各个方面作为独立的对象,如引擎、轮胎和座位等。根据需要,可以在运行时组合这些对象来构建不同类型的车辆,从而更灵活地满足需求。
-
游戏角色的设计:在游戏开发中,角色通常具有各种特定的能力和属性。如果使用继承,可能会创建基础的"Character"类,并从中派生不同类型的角色,如"Player"和"Enemy"等。然而,当需要给角色添加额外的能力或在运行时动态改变角色的行为时,继承关系可能会变得复杂且不灵活。通过使用组合,可以将角色的各个方面表示为独立的组件,如移动组件、攻击组件和装备组件等。根据角色的需求,可以组合这些组件来构建具有不同能力和属性的角色,并且可以在运行时动态地添加、删除或替换这些组件
组合优越于继承的实例:
假设我们正在设计一个模拟动物园的程序,并且需要考虑动物是否具有飞行能力。我们可以使用继承或组合来实现这个关系。
继承的实现方式: 我们可以创建一个Animal类,并从中派生出一个FlyingAnimal
子类。FlyingAnimal
将从Animal
继承所有的属性和方法,并在其中添加飞行相关的特性和行为
public class Animal{
private String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(name+"is eating");
}
}
public class FlyAnimal extends Animal{
public FlyAnimal(String name){
super(name);
}
public void fly(){
System.out.println(name+"is flying");
}
}
这种使用继承的方式看起来很直观,但它存在一些问题
首先,它限制了每个动物对象只能是飞行动物或非飞行动物之一
其次,如果我们想要给动物添加其他功能(例如游泳、奔跑等),就需要不断地派生新的子类,导致类层次结构的膨胀,繁琐
2.组合的实现方式: 相反,我们可以使用组合来实现动物和飞行能力之间的关系。我们将创建一个独立的FlyingAbility
类,并将其作为Animal
类的一个成员变量
public class FlyAbility{
public void fly(String name){
System.out.printfln(name+"is flying");
}
}
public class Animal{
private String name;
private FlyAbility flyAbility;
public Animal(String name, FlyAbility flyAbility){
this.name = name;
this.flyAbility = flyAbility
}
public void eat(){
System.out.println(name+"is eating");
}
public void fly(){
flyAbility.fly(name);
}
}
使用组合,每个Animal
对象都可以包含一个飞行能力对象,并且可以在运行时动态地设置或更改飞行能力。这提供了更大的灵活性和可扩展性。如果我们要给动物添加其他能力,只需简单地添加相应的类并将其组合到Animal
中,而无需修改现有的类层次结构
十、异常(Exception)
异常类型:
1. 编译时异常(受检异常Checked Exception)
在编译时就能够被检查到的异常,需要在代码中显式地捕获或者声明抛出。
2. 运行时异常(非受检异常Unchecked Exception)
在运行时才会被检测到的异常,通常是由于程序逻辑或其他因素导致的异常情况,不需要在代码中显式地捕获或者声明抛出
常见异常:
均继承于Throwable类
1.RuntimeException 运行时异常的父类
2.Exception 所有受检异常的父类
3.Error 系统级异常,通常无法恢复
处理方法:
1.throws:
上报:在方法声明的位置上使用 throws 关键字抛出,谁调用我这个方法,我就抛给谁
public void someMethod throws Exception1, Exception2{
//可能会抛出Exception1或Exception2的代码段
}
2.try{可疑代码}catch(Exception){//对异常进行处理}
如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块
如果异常没有发生,则顺序执行try的代码块,不会进入到catch
如果希望发不发生异常都进入到某段代码段 例如:关闭数据库的连接,则使用 finally{ }
十一、基本数据类型和包装类
基本数据类型
最基础的数据类型
包括 boolean、byte、short、int、long、float和double
可以直接存储在内存中,并且它们具有固定的大小和默认的初始值
包装类
用来封装基本数据类型的类
包括 Boolean、Byte、Short、Integer、Long、Float、Double和Character
包装类可以将基本数据类型转换为对象,并且它们提供了许多额外的方法和属性,以便于操作数据
区别 :
在使用集合类(如List、Set和Map)时,只能使用对象数据类型,而不是基本数据类型
如果想要在集合类中存储基本数据类型,必须使用相应的包装类
十二、Lambda表达式
lambda表达式通常用于代替匿名内部类来实现函数接口,在lambda表达式中,可以直接指定这个方法的实现,而不需要创建匿名内部类
lambda表达式的基本结构
//lambda表达式 基本结构
(parameter1, parameter2...) -> { //表达式体}
//当表达式体只有一条语句时,可以省略大括号
(parameter1, parameter2...) -> {//表达式体}
-
参数列表:括号中的参数列表指定了Lambda表达式所接受的参数。可以根据需要定义零个或多个参数。
-
箭头操作符:箭头操作符"->"将参数列表与Lambda表达式的主体分隔开来。
-
表达式主体:箭头操作符后面的花括号中,可以编写实现函数接口抽象方法的代码。
示例:
interface MyMethod{
//定义了一个名为MyMethod的函数式接口,它只有一个抽象方法doSomething()
void doSomething();
}
public class test{
//在test类中,我们使用Lambda表达式创建了一个MyMethod接口的实例,并实现了doSomething()方法
public static void main(String[] args) {
MyMethod method = () -> {
//在Lambda表达式的主体中,我们简单地打印了一条消息
System.out.println("Hello");
};
method.doSomething();
}
}
注意:
lambda表达式只支持访问外部的常量和最终变量,如果访问变量(值不固定,发生了变化)则会报错
十三、引用类型和对象类型的区别
引用类型:
指引用变量的类型
指定了这个变量能够引用哪些类型的对象
对象类型:
指对象所属的类或接口类型,它定义了对象具有的属性和方法
例如:
Person p = new Student();
引用类型是Person ,对象类型是Student
引用变量:
引用变量本身并不存储对象的数据和方法,它只是指向该对象在内存中的位置;可以将引用变量看作是一个指向对象的指针
变量名可以看作是引用变量的一种形式,即例如中的p