实现OOP的步骤:
分析问题所需的角色,涉及角色本身属性及其只能,涉及多个对象之间的交互,再将对象协同解决问题。
泛型
//类级泛型
class Container<T,U...>{
...}
//方法级泛型
<T,U> U change(T t){
...}
static <T> float change(T t){
...}
// <? extends T> 定义类型的上边界 类型可以是T或者是T的子类
// <? super T> 定义类型的下边界
//泛型的定义规范了容器中存放的对象类型,针对“容器中不能存放基本类型”的问题,通过自动拆装箱机制来解决。
泛型类型参数必须是指定的,否则就会报类型不兼容的错误。
.map(arr ->
new Map.Entry<Integer, Integer>() {
//此处必须要有两个Integer
@Override
public Integer getKey() {
return arr[0];
}
@Override
public Integer getValue() {
return arr[1];
}
@Override
public Integer setValue(Integer value) {
return null;
}
}
)
外松内紧原则
public class Box<T> {
private T contents;
public Box(T contents) {
this.contents = contents;
}
public T getContents() {
return contents;
}
public void setContents(T contents) {
this.contents = contents;
}
public <U extends T> Box<U> createBox(U contents) {
return new Box<U>(contents);
}
}
//返回值为子类对象,用父类泛型引用去接。
封装
定义:控制访问权限(只对需要的类可见)将数据和操作数据的方法捆绑在一起,形成一个合理的单元,通过限制对内部数据的直接访问,提供公共接口来控制数据的访问和操作。封装可以隐藏对象的内部实现细节,使得对象具有良好的抽象性和独立性。
对象:类的实例化
- 状态(Attributes/Fields):对象具有一组属性(也称为字段或成员变量),用于描述对象的特征和状态。这些属性可以是基本类型(如整数、布尔值等)或其他对象类型。(设计作用域管理,命名等问题。)
- 行为(Methods):对象可以执行一些操作,这些操作被定义为对象的方法。方法表示对象的行为和功能,可以访问和修改对象的状态。
- 标识性(Identity):内存地址,对象的引用。
- 封装性(Encapsulation):对象将其状态和行为封装在一起,对外部提供有限的访问接口。通过封装,对象隐藏了内部的实现细节,使得对象的使用者只需关心如何使用对象,而无需了解其内部的具体实现。(基于类)
- 继承性(Inheritance):通过继承机制,一个对象可以从另一个对象(父类)继承属性和方法。继承允许创建对象的层次结构,提供了代码重用和扩展的机制。
- 多态性(Polymorphism):多态性允许不同类型的对象对同一个消息作出不同的响应。通过多态性,可以编写通用的代码,可以处理不同类型的对象而不需要针对每个类型编写特定的代码。
- 单一职能型。
对象作用域
{
int x = 11;
{
int x = 12;
}
}
虽然作用域满足,但是在java中并不允许这么写。
引用
java中的引用只针对对象而言,对基本数据类型而言只能进行值传递,为了使基本数据类型实现引用传递(无指针),将基本数据类型定义为一个包装类的对象。
public class Main {
public static void main(String[] args) {
// 原始数据类型
int num = 10;
// 使用Integer包装类将基本数据类型按引用传递
Integer wrapper = new Integer(num);
increment(wrapper);
// 输出修改后的值
System.out.println("Modified value: " + wrapper);
}
public static void increment(Integer wrapper) {
// 修改包装类中的值
wrapper = wrapper + 1;
}
}
内存之字符串常量池
StringBuilder和String的内存分配方式
StringBuilder和String在内存分配和使用上有一些区别。
- 内存分配:String是不可变的,每次对String进行修改(拼接、替换等操作)都会创建一个新的String对象。每个String对象都会占用一定的内存空间。而StringBuilder是可变的,它使用一个可变的字符数组(动态扩容)来存储字符串内容,可以进行原地修改,不会每次都创建新的对象。
- 字符串常量池:字符串常量池是Java中的一个特殊区域,用于存储字符串字面量(通过双引号创建的字符串)。String对象的字面量形式,例如
String str = "Hello";
,会直接存储在字符串常量池中。而StringBuilder创建的字符串对象不会存储在字符串常量池中,它们在堆内存中分配空间。
StringBuilder避免了频繁创建对象分配内存导致内存浪费。
接口
定义
一种约定(不同的类以统一的方式进行交互) 标准 定义
分类
具象接口:定义一组方法签名(方法名,返回类型,定义),衔接和交互,并行开发前提。
广义接口:通信/网络协议,API接口。
公共接口:公共接口可以被其他包中的类访问和实现,而单独的接口文件可以根据其访问修饰符(public
、protected
、private
等)限制其可访问性。
标记接口(Iterator:可以被迭代,Serialzable:可序列化):不含任何方法的接口。
通过Java的反射机制来判断某个类是否实现了某个标记接口,从而进行相应的处理。
实例1:公共接口的实例
public interface MyInterface {
void someMethod();
}
API(Application Programming Interface)
定义:连接在不同的软件和组件之间,实现不同软件组件之间的数据交换和功能调用,可以通过已有的软件组件构建新的程序。
- 方法和函数的签名:定义了方法或函数的名称、参数列表和返回类型。
- 数据结构和数据格式:定义了数据的组织方式和表示格式。
- 错误处理机制:定义了错误码、异常处理方式等。
- 授权和权限管理:定义了访问和使用API的权限和限制。
- 使用示例和文档:提供了使用API的示例代码、文档和说明。
面向接口原则
接口应具备单一职责
接口应该越小/具体越好
接口方法的实现类和调用接口方法的类相互独立。(将接口作为属性私有化保证了这一点)
调用接口方法的类直接通过作为属性的接口对象调用方法。
一个接口可以由多个实现类实现(多态)。
关于独立
- 更加灵活,耦合度低
- 并行开发,无需等到其他成员实现接口方法后才能开发。
抽象
依赖抽象四大原则
- 单一职责原则:一个类应该只有一个单一的职责或功能。
- 开放封闭原则:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- 里氏替换原则(Liskov Substitution Principle,LSP):子类型必须能够替换掉它们的基类型,而不会引起系统错误或异常。这意味着子类必须能够在不影响系统行为的情况下替代父类。(子类中的隐含父类对象)
- 接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖于它不需要使用的接口。这意味着将接口设计得小而专一,而不是大而全。通过精心设计接口,可以避免不相关的代码之间的耦合,提高系统的灵活性和可扩展性。
这些原则通常被视为面向对象设计的基石,可以帮助开发人员构建可维护、可扩展和易于理解的代码。它们相互关联,共同促进了良好的软件设计实践。
回调接口
作用:实现了低层模块和高层模块之间与接口的交互。
伪代码模板
// 回调接口
public interface Callback {
void onComplete(String result);
}
// 实现回调接口的类
public class CallbackImpl implements Callback {
@Override
public void onComplete(String result) {
System.out.println("操作完成,结果为: " + result);
}
}
// 调用回调接口的类
public class Caller {
private Callback callback;
public void setCallback(Callback callback) {
this.callback = callback;
}//也可使用构造方法
public Caller(Callback callback){
this.callback = callback;
}
public void performOperation() {
// 执行操作
String result = "Operation completed"; // 假设操作完成后的结果
// 调用回调方法将结果传递给回调对象
if (callback != null) {
callback.onComplete(result);
}
}
}
// 使用回调接口的示例
public class Example {
public static void main(String[] args) {
Caller caller = new Caller();
Callback callback = new CallbackImpl(); // 创建实现回调接口的对象
caller.setCallback(callback); // 设置回调对象
caller.performOperation(); // 执行操作并触发回调
}
}
实例1:回调接口
public interface Calculator {
int sum(int ...nums);
}
public class ZSCalculator implements Calculator{
@Override
public int sum(int... nums) {
int rst = 0;
if(nums.length == 1){
rst = nums[0];
}
else if(nums.length > 1) {
int size = nums.length / 2;
for (int i = 0; i < size; i++) {
rst += nums[i] + nums[nums.length - 1 - i];
}
if (nums.length % 2 == 1) {
rst += nums[size];
}
}
return rst;
}
}
public class LSSum {
private Calculator calculator;
public LSSum(Calculator calculator) {
this.calculator = calculator;
}
public int sum(String[] data) {
int rst = 0;
for (String datum : data) {
if (datum.matches("^\\d+(,\\d+)*$")) {
String[] split = datum.split(",");
int[] array = new int[split.length];
for (int i = 0; i < split.length; i++) {
array[i] = Integer.parseInt(datum);
}
rst += calculator.sum(array);
}
}
return rst;
}
}
public static void main(String[] args) {
Calculator calculator = new ZSCalculator();
LSSum lsSum = new LSSum(calculator);
String[] data = {
"1,2,3", "4,5,6"};
int result = lsSum.sum(data);
System.out.println("Sum: " + result);
}
解释:实例1中的ZS是低层模块,只负责根据抽象接口实现具体功能,LS是高层模块,不应该通过创建低层模块实例的方式调用sum方法,而是应该将Calculator作为属性,而不是具体的实现类(体现了面向接口原则),调用被ZS实现过的sum方法。
抽象类和抽象方法
public abstract class MyClass{
//属性(静态常量)
//构造方法(本身无法实例化,但是可供子类调用)
//非抽象方法
//抽象方法
//访问修饰符 abstract 返回类型 方法名称(参数列表);
}
属性/方法 进行抽象化的原则
以下情况可能适合使用静态成员变量:
共享数据:如果某个变量需要在类的所有对象之间共享,可以将其声明为静态变量。静态变量在内存中只有一份副本,被类的所有对象所共享。
常量:如果某个变量的值在类的所有对象中都是相同且不会发生变化的,可以将其声明为静态常量(使用
final static
修饰符)。
以下情况可能适合使用静态方法:
- 工具类方法:如果某个方法与对象的状态无关,只提供一些通用的功能或计算,可以将其声明为静态方法。
- 访问静态成员:如果一个方法只需要访问类的静态成员变量或调用静态方法,可以将该方法声明为静态方法,避免创建类的实例。
其他判断标准:通用性,只执行一次,非数据类(避免记忆效应),底层(LinkNode中的Node类)
抽象类 | 接口 | |
---|---|---|
本质 | 类 | 纯抽象 |
属性 | 静态常量 | 静态常量 |
方法 | 抽象+非抽象 | 抽象 |
重写 | 除抽象类之外,子类必须重写父类的方法。(静态方法、私有方法不可重写,只能重写抽象方法和非私有的非静态方法。) | 除抽象类和接口之外,实现类必须实现接口中的所有方法。 |
可用的访问修饰符 | 所有 | public/默认 |
能不能有构造方法 | 能 | 不能 |
多态:
定义
同一个行为具有不同的表现形式,是一个类实例(对象)的相同方法在不同情形下具有不同的表现形式。
封装和继承是多态的基础
关于封装:父类中的name属性和sound()方法就进行了封装成为了一个类,是实现继承的基础,自然也是让方法能够被不同对象差异化实现的基础。
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void sound() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void sound() {
System.out.println("喵喵喵");
}
}
public class Main {
public static void makeSound(Animal animal) {
animal.sound();
}
public static void main(String[] args) {
Dog dog = new Dog("旺财");
Cat cat = new Cat("小花");
makeSound(dog); // 输出:汪汪汪
makeSound(cat); // 输出:喵喵喵
}
}
编译型多态(静态多态)
定义
方法重载(在编译的时候根据方法的参数类型、返回类型、方法名称来确定具体调用哪个方法,方法调用和方法实现绑定)
检查 关系对象是否是合理的
运行时多态(动态多态)
定义
方法重写和动态绑定(运行时,根据对象的实际类型确定调用哪个方法的过程)
接口的多态(属于运行时多态)
- 接口中定义了公共方法,不同类实现接口完成不同的方法实现。
- 接口中定义了对象引用,不同类实现接口调用了该对象引用的不同方法,
Shape circle = new Circle();
Shape rectangle = new Rectangle();
circle.calculateArea(); // 运行时调用 Circle 类的 calculateArea()
rectangle.calculateArea(); // 运行时调用 Rectangle 类的 calculateArea()
在这个例子中,编译器会检查所调用的方法是否存在于声明的 Shape 类中(编译型多态),而具体执行哪个子类的方法是在运行时决定的。(运行时多态)
完整实现多态需要满足的条件
- 继承
- 子类重写父类方法
- 父类引用指向子类对象
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Cat extends Animal {
void makeSound() {
System.out.println("Meow!");
}
void scratch() {
System.out.println("The cat is scratching");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("Woof!");
}
void wagTail() {
System.out.println("The dog is wagging its tail");
}
}
Animal animal = new Cat();
animal.makeSound(); // 编译时类型是Animal,运行时调用Cat类的makeSound方法,输出"Meow!"
关于父类引用指向子类对象是否能够访问到子类重写的方法?
- 编译时,编译器根据引用类型(
Animal
)来选择合适的方法,它只知道animal
是Animal
类型,并且makeSound
方法在Animal
中是定义好的。所以,尽管编译时多态性不允许我们直接访问子类特有的方法和属性,但它仍然是一种多态的形式。它使得我们可以在编码时以更通用、灵活的方式处理对象,而不需要关心具体的子类类型。 - 运行时,通过动态绑定,决定调用了Cat中的方法。
instanceOf
定义
A instanceOf B A为一个对象,B为一个类,即判断A是否为B类及其子类所创建的实例,返回true则代表B是A的父类。
继承链
继承链的设计
父类与子类的转化
- 父类不允许强制转子类
父类引用装着子类对象,能够转为子类(实际上就是子类对象),也能够自由地访问父类中的属性和方法,但是不能访问子类中新增的属性或者方法(可以调用子类重写的方法)
子类允许转为父类
如何深入了解父类引用装着子类对象的方法域?
- 在编译时期,编译器识别到了父类引用,因此能够调用父类定义的属性和方法
- 在动态绑定时期,根据对象的实际类型(子类)绑定到了子类的重写方法。
是否允许强制转化的判断标准
向上转型
- 体现:将子类对象指向父类的引用(将子类对象自动转为父类对象)
- 特性:是安全的,因为子类对象之中包含了父类对象的所有属性和方法,可被看为父类对象值中的一种形式
向下转型
- 体现:将父类对象强制转型为子类对象
- 特性:是不安全的,因为可能父类引用实际上指向的并不是一个子类对象,此时则没有办法进行类型转化,因此在转化之前需要用instanceOf进行判断
class Animal {
}
class Dog extends Animal {
}
Dog dog = new Dog();
Animal animal = dog; // 向上转型
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
}
继承类的位置
可以在父类内部,也可以在外部。
内部:与父类的联系更加紧密,继承类能够直接对父类的属性和方法进行调用和修改,关系清晰,方便维护和管理。
外部:联系不太紧密。
类、属性与方法
类
访问修饰符+特殊修饰符+class+类名{
属性
方法
}
类类关系
多用组合,少用继承
组合和继承的区别
如何理解组合是在运行期绑定?
当容器类在调用组合类中的方法的时候,不知道该引用真正指向的实例对象是谁。需要到运行时进行进一步的确认。
⭐什么时候需要运用组合关系?
常用于描述一种“拥有”(has-a)的关系。
一个类需要访问另一个类的属性和方法。
一个类依赖于另一个类的功能来实现自己的某些操作。
一个类由多个其他类的对象组成,组成了更复杂的对象结构。
⭐类类关系扩展之代理模式
需求:代理模式可以用于在不改变原有类结构的情况下,对其进行功能扩展或功能增强,同时保持了原有类和代理类之间的独立性。(需要在原有类的基础上添加一些方法模式,但是又不属于继承关系,于是利用代理模式)
主题(Subject):定义了真实对象和代理对象共同的接口,这样代理对象就可以通过实现该接口来对外提供服务。
真实对象(Real Subject):即被代理的对象,代理对象通过调用真实对象的方法来完成实际的业务逻辑。
代理对象(Proxy):包含一个指向真实对象的引用,并实现了主题接口。代理对象会在调用真实对象之前或之后,进行额外的操作,例如记录日志、权限校验等。
伪代码模板
// 主题接口
interface Subject {
void doAction();
}
// 真实对象
class RealSubject implements Subject {
public void doAction() {
System.out.println("执行真实对象的操作");
}
}
// 代理对象
class Proxy implements Subject {
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
public void doAction() {
// 在调用真实对象之前进行额外的操作
System.out.println("代理对象处理事务之前的操作");
// 调用真实对象的方法
realSubject.doAction();
// 在调用真实对象之后进行额外的操作
System.out.println("代理对象处理事务之后的操作");
}
}
// 使用代理对象
public class ProxyExample {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.doAction();
}
}
应用场景:假设我们有一个应用程序需要从远程服务器获取数据,并且我们希望在每次请求前后添加一些额外的逻辑(例如身份验证、缓存等),这时候可以使用代理模式。
// 主题接口
interface DataFetcher {
void fetchData();
}
// 真实对象
class RemoteDataFetcher implements DataFetcher {
private String url;
public RemoteDataFetcher(String url) {
this.url = url;
}
public void fetchData() {
System.out.println("从远程服务器获取数据:" + url);
// 实际的网络请求逻辑
}
}
// 代理对象
class ProxyDataFetcher implements DataFetcher {
private DataFetcher realDataFetcher;
public ProxyDataFetcher(DataFetcher realDataFetcher) {
this.realDataFetcher = realDataFetcher;
}
public void fetchData() {
// 在请求数据之前进行身份验证等操作
System.out.println("进行身份验证");
// 调用真实对象的方法
realDataFetcher.fetchData();
// 在请求数据之后进行缓存等操作
System.out.println("缓存数据");
}
}
// 使用代理对象进行网络请求
public class ProxyExample {
public static void main(String[] args) {
String url = "http://example.com/data";
DataFetcher realDataFetcher = new RemoteDataFetcher(url);
DataFetcher proxyDataFetcher = new ProxyDataFetcher(realDataFetcher);
proxyDataFetcher.fetchData();
}
}
类的初始化顺序
原因:当我们使用一个类时,需要首先加载类的定义和静态成员。这样可以确保在创建类的任何实例之前,静态成员已经被正确地初始化和准备好使用。
数据类
通常只包含数据,get和set的标准方法,一些特殊方法(hashCode,toString,equals),同时也会实现一些特殊的接口比如说(Serializable,Comparable)以便对类中的数据进行序列化或者比较的操作,不含有太多的业务逻辑和方法。
面向用户的数据类是不允许修改的
数据类的作用
数据类的作用 | 应用场景 |
---|---|
表示一个简单的数据结构(可以作为参数传递) | 表示二维坐标、颜色、日期时间 |
封装一些配置信息 | 对数据进行按类别(类别中也可以提取出新的类别,比如说年中可以提取出月和日)分析,需要将所有的类别封装起来,相当于是表头的列。数据库连接信息,应用程序的配置。(通常 还配备了一些私有的属性以及公共的方法来获取和存储数据) |
作为其它类的属性 | 其他类可以通过1.调用该属性的公共方法操作数据2.通过get方法获取数据 |
简单的数据结构的表示
//二维坐标
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Point p = new Point(1,2);
p.x;
p.y;
内部类和匿名内部类
// 在一个类文件中只能有一个公共的类,但是可以有多个类
// 类里面也是可以写类的,里面的类就叫内部类
// .java下有多个.class,这些.class之间相互独立,而内部类则不会显示在其中
// 内部类可以自由访问外部类的资源
package oop06Student;
import java.util.function.Function;
class Test03{
}
public class TestClass {
//内部类
class InnerClass{
}
//匿名内部类:常常出现在方法体,逻辑代码内,返回一个接口对象(即 调用了该接口)
//内部类可以自由访问外部类的资源
public Function<Integer,String> get(){
return new Function<Integer, String>() {
@Override
public String apply(Integer integer) {
return String.valueOf(integer);
}
};
}
}
匿名内部类
匿名内部类是一种特殊的内部类,它没有显式的类名,并且通常用于创建只需使用一次的简单类或接口实例。
匿名内部类的语法格式如下:
javaCopy codeinterface MyInterface {
void doSomething();
}
public class MyClass {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public void doSomething() {
// 实现接口的方法
System.out.println("Doing something...");
}
};
myInterface.doSomething();
}
}
在上述示例中,我们创建了一个匿名内部类实现了MyInterface
接口,并在匿名内部类中重写了doSomething
方法。通过实例化这个匿名内部类对象,我们可以调用doSomething
方法。
匿名内部类的特点包括:
- 没有显式的类名:匿名内部类没有命名,它直接通过实例化接口或类来创建对象。
- 内部类位置:匿名内部类通常作为方法内部的局部变量或方法参数出现,也可以作为类的成员变量。
- 重写方法:匿名内部类可以重写父类或接口中的方法。(可选择一个或者多个)
- 作用域限制:匿名内部类的作用域通常限定在创建它的方法内部,对外部是不可见的。
匿名内部类的使用场景包括:
- 实现接口:当我们需要实现某个接口的方法,并且这个实现只需要在一个地方使用时,可以使用匿名内部类来简化代码。
- 继承抽象类或父类:当我们需要继承一个抽象类或父类,并且只需要在一个地方使用时,可以使用匿名内部类来实现继承并重写方法。
对1,2来说,接口和抽象类都定义了某组方法签名,实现类必须重写相应的方法。
- 构造出返回类型所需要的对象,固定泛型类型。
为什么匿名内部类是一种类?
编译器
在编译过程中,编译器会为它生成一个唯一的类名,并且可以通过反编译工具查看匿名内部类的字节码文件,进一步证实它是一个类。
特征语法结构
特征:1.和类一样,匿名内部类能够实现一个接口或者继承一个类,只是相较于普通类而言,匿名内部类是因它所产生的。
2.类体:匿名内部类在创建时会定义类体,即类中的方法和成员变量。这些方法和变量可以在匿名内部类中进行定义和实现。
3.实例化:匿名内部类可以通过实例化操作new来创建对象,使用类似于创建普通类对象的语法。
4.调用:可以通过匿名内部类对象调用其定义的方法和访问其成员变量,就像调用普通类对象一样。
匿名内部类和Lambda表达式的选用判标
共同点:通常都用于函数式接口的实例的创建
匿名内部类:1.接口或者抽象类中有多个方法需要实现
2.需要在匿名内部类中添加额外逻辑
3.希望访问不止于常量的外部变量
lambda表达式:1.表达式简洁
2.只有一个抽象方法需要实现
3.特定场景:排序,过滤,映射
局部类与全局类
局部类:在psvm里定义的类,作用域仅限于psvm方法中
全局类:在psvm外定义的类,局部类可以调用全局类
属性
访问修饰符+特殊修饰符+数据类型+属性名
方法
访问修饰符+特殊修饰符+返回类型+方法名+(参数列表){
方法体
}
方法的意义:定义对象的行为和功能的。
eg:mark()方法更好地应该被定义在批阅试卷这一类中,用mark()方法来实现批阅试卷类对象的批阅功能,而没有被定义在Teacher类中,老师只是调用批阅试卷这一方法的对象,
由于一个类只能实现一种功能而此处老师类更多倾向于管理信息的一个类。
在面向对象编程中,判定方法应该被定义在哪一个类中,需要考虑以下几个方面:
单一职责原则:一个类应该只负责实现自己的职责和功能,而不应该负责其他类的职责和功能。因此,方法应该定义在与其职责和功能相关的类中。
依赖倒置原则:高层模块不应该依赖于底层模块,而是应该依赖于抽象。因此,方法应该定义在抽象类或接口中,而不是定义在具体实现类中。
开放封闭原则:一个类应该对扩展开放,对修改封闭。因此,方法应该定义在适当的抽象类或接口中,以便于扩展和修改。
继承和多态原则:子类应该能够继承和重写父类的方法,实现多态性。因此,方法应该定义在父类中,以便于子类重写和实现多态性。
是否直接面向数据源也可以作为判定的其中一个因素。
修饰符
public(同包子类)–默认(同包)–protected(异包子类)–private(异包)
由于子类是父类的扩展,会有更多限制的方法特性或者属性,所以子类的限制性会更强。
子类修饰符权限应该<=父类
如果子类的访问权限比父类更大,就意味着子类可以访问更多的成员变量和方法,这可能导致对父类的封装性遭到破坏。父类可能有意将某些成员变量或方法声明为私有或受保护,以限制对其的访问。如果子类可以访问这些私有或受保护的成员,就可能破坏了封装性。
子类的异常数量/异常大小<父类
子类方法不能抛出比父类方法更多的异常,可以不抛出异常或抛出父类方法声明的异常或其子类异常。
入口方法
定义:Java虚拟机(JVM)在执行Java程序时默认要调用的方法。它接受一个字符串数组参数args
,可以用来接收命令行参数。
类的属性可以分为启动类,辅助类,库类(入口方法数量为0~1)。
构造方法
作用:类加载到内存之后,给类属性分配内存空间之后用于类属性的初始化。
属性的初值
字符串 | null |
---|---|
整型 | 0 |
布尔值 | false |
构造方法与普通方法的异同
构造方法 | 普通方法 | |
---|---|---|
是否有返回值 | 无 | 有且只能有一个(返回值可能为空) |
方法个数 | 一个以上(默认无参) | |
调用 | this(参数)/new 对象(利用传入参数完成对象的初始化) | 类内直接调用,类外需判断是静态/实例,对实例来说因为类的封装性需要实例化调用,静态则是通过类名.方法直接进行调用。 |
作用域 | 通常开放 | 取决于访问修饰符 |
调用顺序 | 沿着继承链由上至下,因为只有将父类正确初始化后,由于子类继承了父类的属性和方法,才能将子类初始化。 |
方法的其他补充
return 返回值/结束方法
入参方式
参数列表
封装入参(三个以上使用) 数据类
自定义对象类型(将参数定义为类型的属性),并通过get方法对参数进行处理。
数组/集合类。(根据方法需求确定封装入参的方式:多个类型/方法处理需求选集合封装)
集合的封装入参(外松内紧思想的体现)
public void process(List<Object> params) { String str = (String) params.get(0); int num = (Integer) params.get(1); boolean flag = (Boolean) params.get(2); // ... }
跳转语句
循环级:continue,break;
方法级:return;
系统级:System.exit(0);
重载
定义:方法名相同,参数不同(参数类型/数量/顺序不同),返回类型可相同可不相同(仅仅返回类型相同无法构成重载)。
关键字
final
常量不需要初始化直接定义为属性,变量需要在构造器中初始化
类:不可被继承
方法:不可被重写
变量:基本类型变量:值不可以被修改
引用类型变量:引用不可以再指向另一个对象
原则
不可被扩展 安全 逻辑不可修改
this
this表示对当前对象的引用,即在一个对象的方法中使用this关键字就等同于把this当成对象的引用,可以访问当前对象的实例变量和调用当前对象的方法。
访问实例变量 this.A=A
调用其他构造方法 this(方法参数)
此时,this()必须位于该构造方法的第一行
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; // 使用 this 关键字访问实例变量 this.age = age; } public Person(String name) { this(name, 0); // 使用 this 关键字调用同一个类的其他构造方法 } }
3.this 直接表示当前对象的引用作方法的返回值
super
- 调用父类的构造方法:在子类的构造方法中,可以使用
super
关键字显式调用父类的构造方法。这样可以在创建子类对象时先执行父类的初始化逻辑,确保父类的状态正确初始化。语法为super(arguments)
,其中arguments
为传递给父类构造方法的参数。 - 访问父类的成员变量和方法:在子类中,可以使用
super
关键字访问父类中的成员变量和方法。这对于子类中存在与父类同名的成员变量或方法时特别有用,可以使用super
关键字明确指定访问父类的成员。例如,super.variable
表示访问父类的成员变量,super.method()
表示调用父类的方法。 - 在子类中调用父类的静态方法:子类可以使用
super
关键字调用父类的静态方法。与实例方法不同,静态方法是属于类而不是对象的,所以在子类中使用super
关键字调用父类的静态方法是合法的。
static(静态补充:静态代码块)
static{
//初始化静态变量
}