简单来说
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类
就像生物一样,狮子和老虎都是动物,可以说狮子继承动物、老虎继承动物。所以继承需要符合的关系是:is-a,父类更通用,子类更具体
从面向对象的特性上说
继承是通过已存在的类作为基础创建的新类(新的功能,技术,也可以复用父类功能),但不能选择性的继承
总结:
子类拥有父类所有属性和方法(包括私有,但无法访问,只是拥有)
子类可以对父类进行扩展
Java 是单继承(单继承:一个子类只能继承一个父类),但可多重继承,多重继承:C 类继承 B 类,B 类又继承 A 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类。这是 Java 继承区别于 C++ 继承的一个特性
基础笔记中提到,方法就是类的行为(功能),并且举了很多例子,告诉我们java提供的类(如StringBuffer)怎么用,方法的参数和返回结果是什么
这里将补充一些更抽象的名词:
构造方法是一个特殊的方法,其名字必须和类相同且没有返回类型(补充:构造方法不能通过 abstract、static、final、synchronized 关键字修饰)
构造器(constructor,也叫构造函数),是在创建对象时被自动调用的特殊方法
因为如果构造方法的名字与类名不相同,JAVA的编译器就会将该方法归结为成员方法,而不会将其认定为构造方法,而构造方法是无返回值类型的,但是成员方法是必须有返回值类型,这就会导致编译通不过
也就是无参构造方法:构造方法没有任何参数
无参构造方法是可省的,程序员并不需要显式的声明无参构造方法,更倾向于把这项工作交给编译器
默认构造方法的目的主要是为对象的字段提供默认值,例:
public class father {
public int age;
public String name;
public static void main(String[] args) {
father father = new father();
System.out.println("name:"+father.name+"\n"+"age:"+father.age);
}
}
该类只有两个属性,并在主方法中实例化
输出的结果是:
name:null
age:0
name 是 String 类型,所以默认值为 null,age 是 int 类型,所以默认值为 0
有参数的构造方法,可以为不同的对象提供不同/相同的值,例:
public class father {
public int age;
public String name;
public father(String inputName,int inputAge){
//方法名和类名相同的叫构造方法
name = inputName;
age = inputAge;
}
public static void main(String[] args) {
father father1 = new father("我爸",40);
father father2 = new father("你爸",40);
System.out.println("我爸的名字是:"+father1.name+" "+"年龄是"+father1.age);
System.out.println("你爸的名字是:"+father2.name+" "+"年龄是"+father2.age);
}
}
输出的结果是
我爸的名字是:我爸 年龄是40
你爸的名字是:你爸 年龄是40
在上面的例子中,构造方法有两个参数(name 和 age),这样的话,我们在创建对象的时候就可以直接为 name 和 age 赋值了
补充
子类是不继承父类的构造器(构造方法或者构造函数)的,只是调用,所以不能重写父类构造器。如果父类是有参构造,子类必须通过super关键字调用父类的构造器以适配参数列表
super关键字:通过super关键字来实现对父类成员的访问,用来引用当前对象的父类
this关键字:代表当前类对象的引用(地址) ,this.属性就是这个对象(当前对象)的属性
在Java中,static 是一个关键字,用于修饰类的成员变量、方法、代码块等,它表示这些成员是类级别的,而不是实例级别的。也就是说,它们属于类本身,而不是类的实例。使用时,直接用类点出来就可以,不需要创建实例,下面是用法:
1.static 变量
在类中使用 static
修饰的成员变量称为静态变量,也叫类变量。静态变量与类相关联,而不是与类的每个实例相关联。因此,静态变量可以被类的所有实例共享。静态变量一般在定义时就赋值,也可以在静态代码块中进行初始化。
例如,下面的代码中定义了一个名为 count
的静态变量:
public class MyClass {
static int count = 0;
}
2.static方法
与静态变量类似,但注意:静态方法中不能访问非静态成员变量和方法,因为它们只能访问静态成员变量和方法。
例如,下面的代码中定义了一个名为 add
的静态方法:
public class MyClass {
static int add(int a, int b) {
return a + b;
}
}
3.static代码块
在类中使用 static
修饰的代码块称为静态代码块,它在类被加载时自动执行。静态代码块中一般用于初始化静态变量。静态代码块中的语句只会被执行一次,且在类被加载时执行。
例如,下面的代码中定义了一个静态代码块来初始化静态变量 count
:
public class MyClass {
static int count;
static {
count = 0;
}
}
需要注意的是,static
关键字可以与访问修饰符(如 public
、private
、protected
)一起使用,以控制静态成员的访问权限。同时,使用静态成员变量和方法时,应该遵循一些最佳实践,比如在多线程环境中,应该考虑静态变量的线程安全性
在Java中,final
是一个关键字,用于修饰变量、方法和类,表示这些元素不可被修改或继承。下面是用法:
1.final
变量
在Java中,使用 final
修饰的变量称为常量,它的值在定义之后不能再被修改。常量一般使用大写字母来表示,并且在定义时必须进行初始化
例如,下面的代码中定义了一个名为 PI
的常量:
javaCopy code
final double PI = 3.1415926;
2.final
方法
在Java中,使用 final
修饰的方法称为最终方法,它不能被子类重写。最终方法在设计框架时比较有用,可以保证框架的稳定性
例如,下面的代码中定义了一个名为 sayHello
的最终方法:
javaCopy codepublic class MyClass {
final void sayHello() {
System.out.println("Hello!");
}
}
3.final
类
和final方法一样
例如,下面的代码中定义了一个名为 MyClass
的最终类:
javaCopy codefinal public class MyClass {
// ...
}
需要注意的是,使用 final
关键字可以提高程序的健壮性和安全性。常量一般用于定义不变的值,可以避免因变量被修改而引起的错误。最终方法和最终类可以避免子类重写或继承,可以保证框架的稳定性
extends 和 implements 实现继承,且所有的类都是继承于 java.lang.Object,当一个类没有继承关键字,则默认继承 Object
由于单继承,enxtends只能继承一个父类
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
相当于覆盖+写(字面意思)
允许子类重新定义父类的方法,方法名、参数、都必须一致,返回类型可以一致或可以变小(数据类型自动转换)。重写方法时不能抛出新异常或比原来更广泛的异常
重写的一些注意事项:
1.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected
2.父类的方法只能被它的子类重写
3.声明为 final 的方法不能被重写(后面补充关键字)
4.声明为 static 的方法不能被重写,但是能够被再次声明(后面补充关键字)
6.构造方法不能被重写(因为构造方法不能继承)
一个类中,方法名相同,而参数不同。返回类型可以相同也可以不同,称为重载
基础笔记中很多类的方法都是重载,这就不举例了
重载的一些注意事项:
1.被重载的方法必须改变参数
2.被重载的方法可以改变访问修饰符
3.被重载的方法可以声明更大的异常
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
当一个父类具有不确定性时,就用抽象类来设计,或理解为一种规范。抽象类不能被实例化,由关键字abstract修饰
引例:
一个father父类:
public class father {
public void speak(){
System.out.println("父类不知道说什");
}
}
和继承他的类
public class son extends father{
@Override
public void speak(){
System.out.println("儿子说中文");
}
}
public class daughter extends father {
@Override
public void speak(){
System.out.println("女儿说英文");
}
}
虽然子类继承并重写父类的方法,但父类的方法体一点用也没有,这就是父类方法不确定性
抽象方法在语法形式上,就是用abstract修饰且没有方法体的方法,使用了抽象方法的类一定是抽象类
父类的抽象方法,其意义在于对子类做了一个规范(必须拥有一个这样的方法),然后根据子类的不同,实现该方法也不同。之所以需要继承,是因为这个方法是这些子类的一个共同属性。或者说,父类要通过这些抽象的方法,提供某种特定的服务,但是实现方式在子类中有所不同,所以在父类中写了抽象方法强制子类实现,保证能够提供这种服务
抽象类特征归纳:
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类
在 Java 中,有两种形式来达到抽象的目的,一种是抽象类,另外一种就是接口
接口是一系列方法的声明、是一些方法特征的集合(抽象),一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含需要类实现的方法
继承接口的类必须实现接口中所有方法(除非这个类是抽象类)
在jdk1.8以前是隐式抽象(方法默认会加上public abstract修饰),这样接口中只有抽象方法,jdk1.8以后多了默认方法和静态方法
public interface interface02 {
void method();
}
public可见的,用其他也许(这就是idea中创建的格式)
public interface interface01 extends interface02{
void speak();
}
可以使用interface关键字创建的同时还能extends继承一个接口,这样一来,实现该(interface01)接口的类不仅要实现这个接口本身,还要实现接口继承的接口
补充:接口可以多继承,使用一次extends后面多个接口用逗号隔开
…implements 接口名称[, 其他接口名称, 其他接口名称…, …] …
public class daughter implements interface01{
@Override
public void speak() {
}
@Override
public void method() {
}
}
重写接口中声明的方法时,需要注意以下规则:
1.类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常
2.如果实现接口的类是抽象类,那么就没必要实现该接口的方法
3.在类实现接口的时候,也要注意一些规则:
一个类可以同时实现多个接口。
一个类只能继承一个类,但是能实现多个接口。
一个接口能继承另一个接口,这和类之间的继承比较相似
抽象类也是类,那么上文的区别依然存在,下面是一些补充:
总之,抽象类和接口都是用于抽象化某些概念,但是它们在语法上有很大的不同,应根据实际需求选择使用抽象类或接口。通常情况下,如果需要提供一些默认的实现,或者想要描述一些具体的事物,就应该使用抽象类。而如果需要定义一些规范,或者要求不同的类实现相同的方法,就应该使用接口
是一种特殊的数据类型,它允许程序员定义自己的枚举类型。枚举类型是一组预定义常量的集合,这些常量在整个程序中是恒定的
enum 名
{
RED, GREEN, BLUE;
}
例:
enum Color
{
RED, GREEN, BLUE;
}
public class Test
{
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
枚举也可以声明在类的内部
public class Test
{
enum Color
{
RED, GREEN, BLUE;
}
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) {
System.out.println(myVar);
}
}
}
封装是将类的数据和方法封装在一起,并对外部程序隐藏内部细节,提供公共接口来访问类的功能。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。封装可以有效保护数据的安全性,减少程序出错的风险
为什么要封装?封装的目的在于:
1.将属性私有
2.创建一对赋取值方法(get and set)并让方法公开以便外部访问
总结:set and get方法就是封装
同一个方法在不同对象上可以有不同的实现方式。多态性有助于减少代码的冗余,增加代码的复用性和可维护性。在 Java 中,多态性主要通过继承和接口实现。具体地说,Java 多态性可以分为编译时多态性和运行时多态性两种形式
编译时多态性:在编译阶段确定方法的重载,即通过方法名和参数类型的不同来实现同一个方法的多种不同形式(所以方法重载和重写就是多态的表现)
运行时多态性:在运行阶段确定方法的实现,即通过继承和接口的方式实现方法的重写和多态性(使用父类引用指向子类对象,再调用某一父类中的方法时,不同的子类会表现出不同的结果)
例:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Cat extends Animal {
@Override
//重写就是多态的表现(编译是多态性)
public void makeSound() {
System.out.println("猫发出喵喵的声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗发出汪汪的声音");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Animal();
Animal a2 = new Cat();
Animal a3 = new Dog();
a1.makeSound();
a2.makeSound();
a3.makeSound();
//↑运行时多态性
}
}
多态存在的三个必要条件
1.继承
2.重写
3.父类引用指向子类对象:Parent p = new Child();
在Java中,包(Package)是一种用于组织和管理类和接口的机制。一个包包含了多个类和接口,这些类和接口可以被其他包或类使用。使用包可以避免类名的冲突,提高代码的复用性和可维护性
可以把包看做是一种文件夹的形式,用于存放相关的Java类文件。一个包中可以包含多个类文件,这些类文件可以被其他类或包引用。Java中的包是一个命名空间,用于解决类名的冲突问题,它可以使开发者更加方便地管理和组织类文件
Java中的包通常使用关键字package来定义。在一个Java类文件的开头,通常会定义该类所属的包名
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等
创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头
包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它
如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中
总之,创建Java包的过程主要是在文件系统上创建一个与包名对应的文件夹,并在Java源代码文件的第一行使用package关键字声明该文件所属的包名
import关键字是用来引入其他包中的类或接口的。使用import关键字可以使得我们在代码中可以直接使用其他包中的类或接口而不需要写出完整的包名。在Java中,所有的类都必须属于某一个包,如果没有显式地指定所属的包,那么它就属于默认包。默认包是不需要使用import关键字来引入的,因为它的作用域限定在同一个目录下。
需要注意的是,如果引入的类名与当前类中的成员变量或方法重名,那么需要使用完整的类名来区分它们。如果两个类中的类名相同,但是它们所在的包不同,那么也需要使用完整的类名来区分它们。
总之,import关键字是Java中用于引入其他包中的类或接口的关键字,它可以使代码更加简洁,避免重复书写完整的包名,方便管理和维护其他包中的类和接口
Java是一种面向对象的编程语言,面向对象是Java的核心思想之一。面向对象是一种程序设计范型,它将程序的实现看作是一个对象的集合,这些对象相互作用以实现程序的功能。在面向对象编程中,程序被组织为类和对象,类是一种抽象的概念,它描述了一组具有相似属性和行为的对象。对象则是类的实例,它是具有一组特定属性和方法的实体。
在Java中,面向对象编程的基本概念包括:
面向对象编程使代码更加灵活、可复用和可维护。Java的面向对象特性使得程序员可以通过抽象、封装、继承和多态等机制来设计和实现复杂的系统,这使得Java成为一种广泛应用于企业级应用、移动应用、游戏开发等领域的高级编程语言
Q:
面向过程和面向对象的区别
A:
面向过程的编程思想主要是从程序执行的角度出发,将程序看作一系列的函数调用和数据处理。是一种按照处理数据的过程来组织程序的编程方法,程序主要由一系列的函数组成。面向过程的编程方法更注重数据的处理,常常采用顺序执行的方式,比如刚开始学Java时写的计算器、输出内容,或者是Leetcode上写的算法题。Java仍然支持面向过程的编程方法。Java中可以使用静态方法(static method)和静态变量(static variable)来实现类级别的数据和函数处理,这种编程方法就具有一定的面向过程的特征。结论:因为类要实例化,性能需求大
而面向对象的编程思想主要是从问题本身的角度出发,将程序看作一系列的对象组成的系统,每个对象都有自己的状态和行为。程序的设计是基于对象的交互,将问题分解为多个对象,每个对象都有自己的属性和方法,然后编写类来定义对象的行为。结论:易维护、易复用、易拓展
总之,面向过程的编程适合于一些简单、线性、逐步执行的任务,而面向对象的编程适合于一些复杂、非线性、交互性强的系统。在实际开发中,Java作为一种面向对象的编程语言,具有封装、继承和多态等面向对象的特性,使得程序更加易于维护和扩展,能够更好地适应现代软件开发的需求
Q:
构造器(Constructor)可否重写(Override)
A:
Constructor(可以叫构造方法也可以叫构造器)是用来初始化对象的特殊方法,每个类都至少有一个构造方法,如果没有定义任何构造方法,编译器会自动生成一个默认的无参构造方法。在Java中,构造方法是不能被继承的,因此也就不能被重写。继承只是子类从父类继承了父类的属性和方法,并不包括构造方法
Q:
重载和重写的区别
A:
Q:
什么是自动拆箱与装箱
A:
自动装箱是指将基本数据类型转换成对应的包装类对象,例:
int a = 10;
Integer b = a; // 自动装箱,将int类型的变量a转换为Integer类型的对象
自动拆箱是指将包装类对象转换成对应的基本数据类型,例:
Integer a = 10;
int b = a; // 自动拆箱,将Integer类型的对象a转换为int类型的基本数据类型
自动拆箱和装箱让程序员在使用基本数据类型和其对应的包装类时更加方便,能够提高程序的可读性和可维护性。但需要注意的是,在自动拆箱时,如果包装类对象为null,则会抛出NullPointerException异常
Q:
怎么理解面向对象编程的三大特性
A:
Q:
怎么理解要创建一个无参构造器
A:
在Java中,每个类都必须有一个构造方法,如果程序员没有显式地定义构造方法,则编译器会自动(默认)生成一个无参构造方法,所以父类必须有一个无参构造器(所以在写业务的时候同时需要 有参和无参,有参用于赋值),父类没有无参,编译则不通过
补充:什么是:没有显式地定义构造方法,例
public class child extends father {
private String age;
public child(String age){
this.age = age;
//上述代码隐藏了super();这就是没有显式地定义构造方法
}
}
Q:
成员变量与局部变量的区别
A:
成员变量是定义在类中的变量,也称为实例变量或字段。与局部变量不同,成员变量的作用域不仅限于类的某个方法或代码块中,而是在整个类中都可以访问。每个类的实例都有自己的一组成员变量副本,因此成员变量的值在不同的对象实例中可以不同
在 Java 中,成员变量可以分为以下两种类型:
实例变量
实例变量是定义在类中,但在方法之外的变量。每个实例都有自己的实例变量副本,它们在对象创建时被初始化,并且可以通过对象来访问。
例如,下面的代码中定义了一个名为 Person
的类,其中定义了两个实例变量 name
和 age
:
javaCopy codepublic class Person {
String name;
int age;
}
静态变量(前文static关键字已经介绍了,这里内容重复)
静态变量是定义在类中,但使用 static
关键字修饰的变量。它们在类加载时被初始化,并且只有一份副本,被类的所有实例共享。可以通过类名来访问静态变量。
例如,下面的代码中定义了一个名为 Counter
的类,其中定义了一个静态变量 count
:
javaCopy codepublic class Counter {
static int count = 0;
}
需要注意的是,成员变量的访问权限可以使用访问修饰符 public
、private
、protected
或者默认访问修饰符控制,访问权限控制了成员变量的可见性和可访问性。成员变量可以存储对象的状态和属性,它们是面向对象编程中重要的组成部分
而局部变量是在方法、代码块或者构造方法中定义的变量,只能在它们所在的作用域内访问。作用域是指变量被定义后能够被访问的区域
局部变量只在它所在的代码块中有效,出了这个代码块就失效了。局部变量必须要初始化后才能使用,否则编译器会报错。在方法调用结束时,局部变量的值会被销毁,这样可以节省内存空间
例如,下面的代码中定义了一个名为 add
的方法,其中定义了两个局部变量 a
和 b
:
javaCopy codepublic int add(int a, int b) {
int sum = a + b; // 定义局部变量 sum
return sum;
}
在这个例子中,a
和 b
是方法的参数,也是局部变量,它们的作用域仅限于方法中。sum
是在方法中定义的局部变量,作用域仅限于方法中的代码块,方法调用结束后,它的值会被销毁
Q:
==和equals
Object类的equals方法默认实现是比较两个对象的引用是否相等,这就导致了obejct类的equals方法和==是一致的。但是java自带的类:string类或者其他基本类型,就重写了equals方法,是比较它们的值是否相等。所以在自己类要是有equals方法时就要重写,以下列举了大多数要重写equals的情况:
1.创建String字符串
在Java中,String对象是不可变的,也就是说,当我们创建一个新的字符串时,实际上是创建了一个新的对象。因此,如果我们使用默认的equals方法来比较两个字符串对象,那么只有当它们的引用相同时,才会返回true。这显然是不合适的,因为我们通常需要根据字符串的内容来判断它们是否相等
2.自定义对象比较
假设我们定义了一个Person类来表示人员信息,它有name和age两个属性。如果我们使用默认的equals方法来比较两个Person对象,那么只有当它们的引用相同时,才会返回true。但实际上我们需要的是比较这两个属性是否相等。(就算两个属性相对,引用不同也不会返回true)
所以需要重写equals方法
而==是操作符,用于比较两个对象的引用是否相等。如果两个对象引用同一个对象,则返回true,否则返回false。在equals不重写的前提下,二者mei’you