类和对象基本概念
一、类的概念
- 定义:类是一种抽象的数据类型,它用于定义对象的属性(也称为成员变量)和行为(也称为成员方法)。可以把类想象成是创建对象的蓝图或者模板,它规定了对象具有哪些特征以及能够执行哪些操作。
- 语法格式示例:
以下是一个简单的Person
类的定义,用于描述人的一些基本信息和行为:
public class Person {
// 成员变量,用来描述对象的属性
private String name;
private int age;
// 成员方法,用来定义对象的行为
public void sayHello() {
System.out.println("Hello, my name is " + name + ", and I'm " + age + " years old.");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在这个 Person
类中:
- 定义了两个成员变量 name
(字符串类型,用于表示人的名字)和 age
(整型,用于表示人的年龄)。
- 定义了一个成员方法 sayHello
,它的作用是输出包含这个人名字和年龄信息的问候语。
这里IDEA可以自动生成 get
和 set
方法,使用快捷键 Alt + Ins
:
二、对象的概念
- 定义:对象是类的实例,也就是根据类这个蓝图实际创建出来的具体存在的个体。例如,基于上述
Person
类的定义,可以创建出多个不同的Person
对象,每个对象都有自己独立的name
和age
值,并且都能执行sayHello
这个行为。 - 创建对象的语法示例:
public class Main {
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "Alice";
person1.age = 25;
Person person2 = new Person();
person2.name = "Bob";
person2.age = 30;
person1.sayHello();
person2.sayHello();
}
}
在上述代码中:
- 通过 new
关键字创建了两个 Person
对象 person1
和 person2
,它们都是 Person
类的实例。
- 然后分别给这两个对象的成员变量赋值,赋予了不同的名字和年龄信息。
- 最后调用它们各自的 sayHello
方法,会输出不同的问候语,展示了不同对象具有各自独立属性的特点。
三、类的构成要素
成员变量:
- 成员变量用于描述类的对象所具有的属性,其声明方式与普通变量类似,只是它们是定义在类的内部。例如在
Person
类中的name
和age
就是成员变量。 - 成员变量可以有不同的访问修饰符(如
public
、private
、protected
等),不同的修饰符控制着这些变量在类的内外不同的访问权限,以此来实现信息的封装。
成员方法:
方法声明格式:
// 声明格式
权限修饰符 [其他修饰符] 返回值类型 方法名(形参列表) [throws 异常类型]{ // 方法头
// 方法体
}
注:[]中的内容不是必须的。
- 成员方法用于定义类的对象所能执行的操作或者行为。像
Person
类中的sayHello
方法就是成员方法,它内部包含了一系列语句,用于完成特定的功能,比如输出一段文本信息。 - 成员方法同样可以设置不同的访问修饰符来控制访问权限,并且可以有参数(用于接收外部传入的数据来执行相应操作)和返回值(将方法执行的结果返回给调用者)。例如,可以定义一个带有参数的成员方法来修改
Person
类中对象的年龄:
public class Person {
String name;
int age;
public void setAge(int newAge) {
age = newAge;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", and I'm " + age + " years old.");
}
}
在上述代码中,setAge
方法就带有一个 int
类型的参数 newAge
,通过调用这个方法并传入具体的年龄数值,就可以改变对应 Person
对象的年龄。
四、对象的生命周期
- 创建阶段:通过
new
关键字创建对象时,会在内存(堆内存)中为对象分配空间,用于存储对象的成员变量等数据,同时会调用对象所属类的构造方法(如果没有显式定义构造方法,Java会提供一个默认的无参构造方法)来进行对象的初始化工作。 - 使用阶段:对象创建好后,就可以通过对象引用(如上述的
person1
、person2
等)来访问对象的成员变量和调用成员方法,完成各种操作。 - 销毁阶段:Java有自动的垃圾回收机制(Garbage Collection,简称GC),当对象不再被引用(即没有任何变量指向它)时,它就有可能被垃圾回收器认定为“垃圾”,在合适的时候回收其占用的内存空间,从而完成对象的销毁。
五、类和对象的关系
类是抽象的概念,它定义了对象的共性和规范;而对象是具体的、实实在在的个体,是类的具体体现。一个类可以创建出多个不同的对象,这些对象有着相同的结构(由类定义),但各自的属性值等可以不同,就如同使用同一张建筑图纸(类)可以盖出多栋不同的房子(对象),每栋房子的内部布局一样(结构相同),但居住的人、装修风格等(属性)可以不同。
一些注意事项
强调1:创建了Person类的两个对象
Person p1 new Person();
Person p2 new Person();
说明: 创建类的多个对象时,每个对象在堆空间中有一个对象实体。每个对象实体中保存着一份类的属性。如果修改某一个对象的某属性值时,不会影响其他对象此属性的值。
强调2:声明类的两个变量
Person p1 = new Person();
Person p3 = p1;
说明: 此时的p1,p3两个变量指向了堆空间中的同一个对象实体。(或即p1,p3保存的地址值相同)。如果通过其中某一个对象变量修改对象的属性时,会导致另一个对象变量此属性的值。(引用类型)
成员变量
- 声明在类内,方法外的变量。
- 随着对象的创建,存储在堆空间中。
- 随着对象的创建而创建,随着对象的消亡而消亡。
- 属性是可以使用权限修饰符进行修饰的(
private
、protected
、public
),默认是public
。而局部变量不能使用任何权限修饰符进行修饰的。 - 属性都有默认初始化值。意味着,如果没有给属性进行显式初始化赋值,则会有默认初始化值。C++中如果不初始化,是一个随机值。局部变量都没有默认初始化值。意味着,在使用局部变量之前,必须要显式的赋值,否则报错。
- 引用类型对象创建时,赋值号右边必须使用
new
的方式。
练习:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定()。
- 问题一:打印出3年级(state值为3)的学生信息。
- 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
- 生成随机数:
Math.random()
,返回值类型double
; - 四舍五入取整:
Math.round(double d)
,返回值类型long
。
年级[1,6]:(int)(Math.random() * 6 + 1)
分数[0,100]:(int)(Math.random() * 101)
Student.java
public class Student {
int number; // 学号
int state; // 年级
int score; // 成绩
// 声明一个方法,显示学生的信息
public String show(){
return "number = " + number + ", score = " + score
+ ", state = "+ state;
}
}
StudentUtil.java
public class StudentUtil {
/**
* 打印指定年级的学生信息
*/
public void printStudentWithState(Student[] students, int state){
for (int i = 0; i < students.length; i++) {
if(students[i].state == state)
System.out.println(students[i].show());
}
}
/**
* 使用冒泡排序法排序数组
*/
public void bubblesortStudent(Student[] students){
for (int i = 0; i < students.length; i++) {
for (int j = 0; j < students.length - i - 1; j++) {
if(students[j].score < students[j + 1].score){
Student tmp = new Student();
tmp = students[j];
students[j] = students[j + 1];
students[j + 1] = tmp;
}
}
}
}
/**
* 遍历指定学生数组
*/
public void printStudents(Student[] students){
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].show());
}
}
}
StudentTest.java
public class StudentTest {
public static void main(String[] args) {
// 创建一个对象数组
Student[] students = new Student[20];
// 使用循环给数组中每个对象赋值--实际创建对象
for (int i = 0; i < students.length; i++) {
students[i] = new Student();
// 给每个对象的属性赋值
students[i].number = i + 1;
students[i].state = (int)(Math.random() * 6 + 1);
students[i].score = (int)(Math.random() * 101);
}
StudentUtil util = new StudentUtil();
// 打印出3年级(state值为3)的学生信息。
util.printStudentWithState(students, 3);
// 使用冒泡排序按学生成绩排序,并遍历所有学生信息
util.bubblesortStudent(students);
System.out.println("-------------------------------------------");
util.printStudents(students);
}
}
方法调用
main
函数不能直接调用一个普通类内方法,因为 main
被 static
修饰:
public class test {
private int year;
int month;
int day;
public void print(){
System.out.println("year = " + year + ", month = " + month + ", day = " + day);
}
public static void main(String[] args) {
print();
}
}
报错信息:
java: 无法从静态上下文中引用非静态 方法 print()
解决办法:在 main
函数中先定义该类对象,通过这个对象调用该方法:
public class test {
private int year;
int month;
int day;
public void print(){
System.out.println("year = " + year + ", month = " + month + ", day = " + day);
}
public static void main(String[] args) {
test t = new test();
t.print();
}
}
注: “同级” 函数可以相互调用,而且不局限于代码书写顺序(写在上面的函数可以调用写在下面的函数)。
文档注释快捷写法:/**
在当前光标的上一行新起一行:Ctrl + Alt + Enter
在当前光标的下一行新起一行:Shift + Enter
重载
- 定义: 在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。满足这样特征的多个方法,彼此之间构成方法的重载。
- “两同一不同”: 两同:同一个类、相同的方法名;一不同:参数列表不同。①参数个数不同 ②参数类型不同(顺序)
注意: 方法的重载与形参的名、权限修饰符、返回值类型都没有关系。
如何判断两个方法是相同的呢?(换句话说,编译器是如何确定调用的某个具体的方法呢?)
如何判断两个方法是相同的呢?当 方法名相同,且形参列表相同(形参列表相同指的是参数个数和类型都相同,与形参名无关)时,就可以说这两个方法是相同的。
编译器是如何确定调用的某个具体的方法呢?先通过方法名确定了一波重载的方法,进而通过不同的形参列表,确定具体的某一个方法。
面试题:
public static void main(String[] args) {
int[] arr = new int[]{ 1,2,3 };
System.out.println(arr);
char[] arr1 = new char[]{ 'a','b','c'};
System.out.println(arr1);
boolean[] arr2 = new boolean[]{ false,true,true};
System.out.println(arr2);
}
arr
和 arr2
作为参数调用的 println()
函数为:
public void println(Object x); // 打印地址
arr1
作为参数调用的 println()
函数为:
public void println(char[] x);
其实就是 println()
的重载:查看方式 Ctrl + 左键单击函数
跳转到相应的库;Ctrl + F12
题目
利用接口编写计算三角形、梯形面积和周长的程序。
它定义了一个接口Shape
用于计算形状的周长和面积,并实现了两个类Triangle
和Trapezoid
来具体计算三角形和梯形的这些属性。
public interface Shape {
double getPerimeter();
double getArea();
}
class Triangle implements Shape {
private double sideA;
private double sideB;
private double sideC;
public Triangle(double sideA, double sideB, double sideC) {
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
@Override
public double getPerimeter() {
return sideA + sideB + sideC;
}
@Override
public double getArea() {
double s = getPerimeter() / 2;
return Math.sqrt(s * (s - sideA) * (s - sideB) * (s - sideC));
}
}
class Trapezoid implements Shape {
private double base1;
private double base2;
private double height;
private double sideA;
private double sideB;
public Trapezoid(double base1, double base2, double height, double sideA, double sideB) {
this.base1 = base1;
this.base2 = base2;
this.height = height;
this.sideA = sideA;
this.sideB = sideB;
}
@Override
public double getPerimeter() {
return base1 + base2 + sideA + sideB;
}
@Override
public double getArea() {
return (base1 + base2) / 2 * height;
}
}
// 示例用法
public class Main {
public static void main(String[] args) {
// 创建三角形实例
Shape triangle = new Triangle(3, 4, 5);
System.out.println("Triangle perimeter: " + triangle.getPerimeter());
System.out.println("Triangle area: " + triangle.getArea());
// 创建梯形实例
Shape trapezoid = new Trapezoid(3, 5, 4, 5, 5);
System.out.println("Trapezoid perimeter: " + trapezoid.getPerimeter());
System.out.println("Trapezoid area: " + trapezoid.getArea());
}
}
在这个例子中,我们创建了两个类来实现Shape
接口,每个类都有其特定的构造函数来设置边长和其他参数。然后我们分别计算了三角形和梯形的周长与面积。请注意,在实际应用中,可能需要对输入数据进行更多的有效性检查,比如确保三角形的三条边能够形成一个有效的三角形(任意两边之和大于第三边),以及梯形的高和底边长度是合理的。
编写一个家电接口和多态应用程序,要求如下:
(1)创建一个ElecDevice接口,它有两个方法turnOn()和turnOff()。
(2)创建一个AudioDevice类和一个Refrigerator类,这两个类实现了ElecDevice接口,AudioDevice有两个方法increaseVol()和decreaseVol()。
Refrigerator类有一个方法setFreezingLevel().
(3)创建AudioDevice有两个子类TV和Radio.TV有两个方法changeChannelO和adjustColor().Radio 有一个方法ajustWavelenth()。TV和Radio覆盖了父类里的increaseVol()和decreaseVol()方法。
子类覆盖的方法中首先调用super父类的方法,然后再添加自己的语句。
(4)创建一个TestElecDevice类,该类从键盘上接收一个命令行参数,输入TV或Radio参数。在这个类中创建两个实例变量ed(ElecDevice类型)和ad(AudioDevice类型)。
在maim()方法中为ed复制一个Refrigerator对象,并调用ed的turnOn()和turnOff()方法。接下来根据输入的参数给ad复制TV或 Radio对象,并调用increareVol()和decreaseVol()
请在每个类的方法里写一个输出语句。
首先,我们定义了ElecDevice
接口:
public interface ElecDevice {
void turnOn();
void turnOff();
}
然后,我们创建了AudioDevice
类,它实现了ElecDevice
接口,并添加了音量控制的方法:
public abstract class AudioDevice implements ElecDevice {
@Override
public void turnOn() {
System.out.println("Audio device is turned on.");
}
@Override
public void turnOff() {
System.out.println("Audio device is turned off.");
}
public void increaseVol() {
System.out.println("Increasing volume.");
}
public void decreaseVol() {
System.out.println("Decreasing volume.");
}
}
接着,我们创建了Refrigerator
类,它也实现了ElecDevice
接口,并添加了设置冷冻级别的方法:
public class Refrigerator implements ElecDevice {
@Override
public void turnOn() {
System.out.println("Refrigerator is turned on.");
}
@Override
public void turnOff() {
System.out.println("Refrigerator is turned off.");
}
public void setFreezingLevel() {
System.out.println("Setting freezing level.");
}
}
现在,我们创建TV
类作为AudioDevice
的子类,并覆盖其方法:
public class TV extends AudioDevice {
public void changeChannel() {
System.out.println("Changing channel.");
}
public void adjustColor() {
System.out.println("Adjusting color.");
}
@Override
public void increaseVol() {
super.increaseVol(); // 调用父类方法
System.out.println("TV volume increased.");
}
@Override
public void decreaseVol() {
super.decreaseVol(); // 调用父类方法
System.out.println("TV volume decreased.");
}
}
同样,我们创建Radio
类作为AudioDevice
的子类,并覆盖其方法:
public class Radio extends AudioDevice {
public void adjustWavelength() {
System.out.println("Adjusting wavelength.");
}
@Override
public void increaseVol() {
super.increaseVol(); // 调用父类方法
System.out.println("Radio volume increased.");
}
@Override
public void decreaseVol() {
super.decreaseVol(); // 调用父类方法
System.out.println("Radio volume decreased.");
}
}
最后,我们创建TestElecDevice
类来测试这些设备:
import java.util.Scanner;
public class TestElecDevice {
private ElecDevice ed;
private AudioDevice ad;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter the device (TV/Radio): ");
String deviceType = scanner.nextLine();
TestElecDevice test = new TestElecDevice();
// 初始化 Refrigerator 对象
test.ed = new Refrigerator();
test.ed.turnOn();
test.ed.turnOff();
// 根据输入初始化 TV 或 Radio 对象
if ("TV".equals(deviceType)) {
test.ad = new TV();
} else if ("Radio".equals(deviceType)) {
test.ad = new Radio();
} else {
System.out.println("Invalid device type!");
return;
}
// 调用音量控制方法
test.ad.increaseVol();
test.ad.decreaseVol();
}
}
在这个程序中,TestElecDevice
类会提示用户输入设备类型,然后根据输入创建相应的AudioDevice
子类的对象,并调用其方法。同时,它还创建了一个Refrigerator
对象,并调用了它的开关方法。这样就可以展示多态的应用以及继承带来的好处。
继承
在Java中,继承是面向对象编程的一个重要特性,它允许创建基于现有类的新类,新类会继承现有类的属性和方法,以下是关于Java中继承的详细介绍:
一、继承的概念
- 定义:继承是一种机制,通过它可以在已有类(称为父类、超类或基类)的基础上创建新类(称为子类、派生类),子类会自动获得父类中除了构造方法之外的所有成员变量和成员方法(包括私有成员,虽然子类不能直接访问私有成员,但可以通过父类提供的公共方法间接访问),并且子类可以根据自身的需求添加新的成员变量和成员方法,或者重写父类中的部分方法。
二、继承的语法
使用 extends
关键字来实现继承关系,其基本语法如下:
// 定义父类
class ParentClass {
int parentVariable;
void parentMethod() {
System.out.println("这是父类的方法");
}
}
// 定义子类,继承自ParentClass
class ChildClass extends ParentClass {
int childVariable;
void childMethod() {
System.out.println("这是子类的方法");
}
}
在上述代码中,ChildClass
是 ParentClass
的子类,它通过 extends
关键字继承了 ParentClass
,所以 ChildClass
的对象除了拥有自身定义的 childVariable
和 childMethod
之外,还自动拥有 ParentClass
中的 parentVariable
和 parentMethod
。
三、继承的作用
-
代码复用:子类无需重新编写父类中已有的代码就能直接使用那些成员变量和成员方法,大大减少了代码的重复编写,提高了代码的开发效率和可维护性。例如,假设有一个
Animal
类,里面定义了动物都有的属性(如体重、年龄等)和一些基本行为(如吃东西、睡觉等),然后再创建Dog
、Cat
等子类去继承Animal
类,这些子类就可以直接利用Animal
类中已有的相关代码,只需关注自身特有的属性和行为(比如狗的吠叫、猫的抓挠等)即可。 -
实现多态:继承是实现多态的基础之一,通过子类重写父类的方法,并结合对象的向上转型等机制,可以让同一个方法调用在不同的对象(父类对象或子类对象)上产生不同的执行结果,这增加了程序设计的灵活性和可扩展性。
四、方法重写(Override)
- 定义:在子类中可以对父类中已有的方法进行重新定义,使其具有不同的实现逻辑,这个过程就叫做方法重写。但要遵循一定的规则:
- 方法签名(方法名、参数列表、返回类型,参数顺序也要相同)必须与父类被重写的方法一致(返回类型如果是基本数据类型必须完全相同,如果是引用数据类型,子类重写后的返回类型可以是父类返回类型的子类型)。
- 重写的方法不能比父类中被重写的方法有更严格的访问限制(例如父类方法是
public
,子类重写后的方法不能是private
或protected
)。 - 重写的方法不能抛出比父类中被重写的方法更多、更宽泛的异常(除非是子类重写后的方法抛出的异常是父类被重写方法抛出异常的子类型)。
以下是一个方法重写的示例:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵喵");
}
}
在上述代码中,Dog
类和 Cat
类都重写了 Animal
类中的 makeSound
方法,使得不同的子类对象调用 makeSound
方法时会发出不同的声音,体现了多态性。
五、访问控制与继承
父类中的成员变量和成员方法有不同的访问修饰符(public
、private
、protected
和默认访问权限),在继承关系中它们有着不同的表现:
public
:父类中声明为public
的成员,在子类中可以直接访问,在类外部(通过对象)也能访问,例如上面例子中的makeSound
方法如果是public
,在Dog
、Cat
子类以及外部创建对象时都能正常调用。private
:父类中声明为private
的成员,子类虽然继承了它们,但不能直接访问这些成员,只能通过父类提供的公共方法来间接访问(如果有的话)。例如,若Animal
类中有个private
的变量记录动物的健康状况,子类就不能直接获取或修改它,除非Animal
类提供了相应的公共访问或修改方法。protected
:父类中声明为protected
的成员,子类可以直接访问,并且在同一个包中的其他类(非子类)也能有一定的访问权限,但在不同包且非子类的类中不能直接访问。- 默认访问权限(无修饰符):在同一个包中的子类可以继承并访问这些成员,但在不同包中的子类就不能访问了,而且在不同包的非子类类中也不能访问。
六、继承的层次结构
Java支持多层继承,也就是子类可以继续作为父类被其他类继承,例如:
class Grandparent {
// 成员变量和方法定义
}
class Parent extends Grandparent {
// 继承Grandparent并添加自身内容
}
class Child extends Parent {
// 继承Parent,间接继承了Grandparent,并添加自身内容
}
在这个例子中,Child
类通过 Parent
类间接继承了 Grandparent
类,从而拥有了 Grandparent
类中的相关成员以及 Parent
类中的成员,同时可以添加自己特有的成员变量和成员方法。不过,Java不支持多重继承(即一个类不能同时继承多个父类),这是为了避免复杂的继承冲突和二义性等问题。
抽象类和接口
在Java编程语言中,抽象类和接口都是用于定义类的蓝图或模板的,但它们有不同的用途和特性。以下是它们的详细介绍:
抽象类 (Abstract Class)
-
定义与用途:
- 抽象类是一个不能被实例化的类,它通常包含一些抽象方法(没有方法体的方法)和一些具体方法(有方法体的方法)。
- 抽象类主要用于表示一种通用概念,并为子类提供通用的属性和方法。
-
语法特点:
- 使用
abstract
关键字修饰类和方法。 - 抽象类可以包含具体方法、抽象方法、成员变量、构造器、初始化块、静态方法、内部类等。
- 一个类继承抽象类时,必须实现其所有未实现的抽象方法,除非这个子类也是抽象类。
- 使用
-
示例:
abstract class Animal { String name; // 构造器 public Animal(String name) { this.name = name; } // 抽象方法,没有方法体 public abstract void makeSound(); // 具体方法 public void sleep() { System.out.println(name + " is sleeping."); } } class Dog extends Animal { public Dog(String name) { super(name); } // 实现抽象方法 @Override public void makeSound() { System.out.println(name + " says Woof!"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog("Buddy"); dog.makeSound(); // 输出: Buddy says Woof! dog.sleep(); // 输出: Buddy is sleeping. } }
接口 (Interface)
-
定义与用途:
- 接口是一个完全抽象的类,它只包含抽象方法(Java 8之前)和常量(静态且final的变量)。从Java 8开始,接口也可以包含默认方法(有方法体的方法)和静态方法。
- 接口主要用于定义对象的行为,它规定了实现该接口的类必须遵循的规则。
-
语法特点:
- 使用
interface
关键字修饰。 - 接口中的方法默认是
public abstract
的,变量默认是public static final
的。 - 一个类可以实现多个接口,用逗号分隔。
- 实现接口的类必须实现接口中的所有抽象方法,除非这个类也是抽象类。
- 使用
-
示例:
interface CanFly { void fly(); } interface CanSwim { void swim(); } class Bird implements CanFly, CanSwim { @Override public void fly() { System.out.println("Bird is flying."); } @Override public void swim() { System.out.println("Bird is swimming (not very well)."); } } public class Main { public static void main(String[] args) { Bird bird = new Bird(); bird.fly(); // 输出: Bird is flying. bird.swim(); // 输出: Bird is swimming (not very well). } }
抽象类与接口的区别
-
继承与实现:
- 抽象类使用
extends
关键字继承,接口使用implements
关键字实现。 - 一个类只能继承一个抽象类,但可以实现多个接口。
- 抽象类使用
-
成员:
- 抽象类可以有具体的方法实现和成员变量,接口在Java 8之前只能有抽象方法和常量(Java 8及以后可以有默认方法和静态方法)。
- 抽象类可以有构造器,接口不能有构造器。
-
设计目的:
- 抽象类主要用于表示一种层级关系,提供通用的属性和方法。
- 接口主要用于定义一种行为契约,确保实现接口的类具有特定的功能。
-
访问修饰符:
- 抽象类可以是
public
、protected
、默认(包访问权限)
或private
,接口默认是public
,且不能用其他访问修饰符修饰(从Java 9开始,接口可以是private
或protected
,但这是针对嵌套接口)。
- 抽象类可以是
通过理解这些概念和区别,开发者可以根据具体需求选择合适的工具来设计和实现类及其关系。
- 接口要被子类实现,抽象类要被子类继承
- 接口中变量全为静态常量,而抽象类中可有普通变量。
- 接口中全为方法的声明,抽象类中可以有方法的实现。
- 接口中不可以有构造函数,抽象类中可以有构造函数。
- 接口可以多实现,而抽象类必须被单继承。
- 接口中方法全为抽象方法,而抽象类中也可以有非抽象方法。