类与接口
抽象类和接口的对比?
抽象类捕捉了子类的通用特性。接口是抽象方法的集合。 从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
相同点:
接口和抽象类都不能实例化
都位于继承的顶端,用于被其他实现或继承
都包含抽象方法,其子类都必须覆写这些抽象方法
不同点:
参数 | 抽象类 | 接口 |
声明 | abstract | interface |
实现 | extends | implement |
构造器 | 可以有 | 不能有 |
访问修饰符 | 任意 | 默认为public,且不可定义为private或protected |
多继承 |
一个类最多只能继承一个抽象类
|
一个类可以实现多个接口
|
字段声明 |
抽象类的字段声明可以是任意的
|
接口的字段默认都是 static 和 final
|
备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
现在,我们可以为接口提供默认实现的方法了,且不强制其子类实现。
接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样的原则:
行为模型应该总是通过接口而不是抽象类定义,通常是优先选用接口,尽量少用抽象类。 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化(需要继承),普通类可以直接实例化。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承。
创建一个对象用什么关键字?对象实例与对象引用有何不同?
new关键字。
new在堆内存中创建对象实例,对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以有一个或没有对象与之关联;一个对象也可以被多个引用同时指向。
加餐小tips:
学过C语言的同学可以这样理解二者的关系:对象实例即为对象开辟的内存空间,对象引用即指向该内存空间的指针(但Java中没有指针的概念)。如果还有些模糊,继续看:
对象实例:它是指一个对象在内存中的具体存在。当我们使用类定义了一个新的对象时,系统会在内存中为这个对象分配一块空间,并将其初始化。每个对象实例都有其独特的内存地址,这也是我们常说的“对象实例化”。
对象引用:它是用来存储对象实例的地址的变量。在Java中,所有的变量(包括基本数据类型和引用数据类型)都是引用类型。当我们声明一个变量并将其初始化为某个对象实例时,这个变量就成为了该对象实例的一个引用。一个对象可以有多个引用,这些引用都指向同一个对象实例。
总结一下,对象实例是内存中具体的存在,而对象引用是用来存储对象实例地址的变量。一个对象可以有多个引用,但每个引用都指向同一个对象实例。
变量与方法
成员变量与局部变量的区别有哪些?
变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域。
区别 | 成员变量 | 局部变量 |
定义 | 方法外部,类内部定义的变量 | 类的方法中的变量 |
作用域 |
整个类
|
只在某个范围内有效。(一般指方法,语句体内)
|
存储位置 |
随对象的创建而存在,随对象的消失而消失,存储在堆内存中。
|
在方法被调用,或者语句被执行的时候存在,存储在栈内存中
|
生命周期 |
随对象的创建而存在,随对象的消失而消失
|
当方法调用完,或者语句结束后自动释放
|
初始值 |
有默认初始值。
|
没有默认初始值,使用前必须赋值
|
使用原则
|
就近原则
|
先在局部范围找,有就使用;没有再去成员位置找
|
在Java中定义一个没有方法体且没有参数的构造方法,有何作用?
本质上是在问无参构造器的作用。Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”(即无参构造器)。如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则将产生编译错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。而无参构造器就是用于规避此类错误产生的。
加餐小tips:
无参构造器的作用 (补充):
初始化对象:无参构造器通常用于给对象的成员变量赋初始值,确保对象在使用前
具有一个明确的初始状态。
提供一个默认的构造方法:当类中定义了至少一个带参数的构造方法时,提供一个无参构造 器可以作为默认的构造方法,以便在不传递任何参数的情况下创建对象。
简化对象创建:无参构造器使得在不传递任何参数的情况下创建对象变得更加简单和直观, 避免了在每次创建对象时都需要提供参数的麻烦。
遵循Liskov替换原则:在面向对象设计中,无参构造器的使用可以确保对象的初始状态一 致,从而遵循Liskov替换原则,使得子类可以安全地替换父类。
防止不必要的构造函数调用:在继承体系中,如果父类和子类都定义了构造方法,那么在创 建子类对象时可能会调用父类的构造方法,可能导致一些不必要的操作。
提供一个默认的构造方法:当类中定义了至少一个带参数的构造方法时,提供一个无参构造 器可以作为默认的构造方法,以便在不传递任何参数的情况下创建对象。
简化对象创建:无参构造器使得在不传递任何参数的情况下创建对象变得更加简单和直观, 避免了在每次创建对象时都需要提供参数的麻烦。
遵循Liskov替换原则:在面向对象设计中,无参构造器的使用可以确保对象的初始状态一 致,从而遵循Liskov替换原则,使得子类可以安全地替换父类。
防止不必要的构造函数调用:在继承体系中,如果父类和子类都定义了构造方法,那么在创 建子类对象时可能会调用父类的构造方法,可能导致一些不必要的操作。
Liskov替换原则(Liskov Substitution Principle,简称LSP)是一组用于创建继承层次结构的指导原则。它要求在继承关系中,客户端代码能够放心的使用任意类或子类而不担心影响所期望的行为。具体来说,如果S是T的子类型,那么所有T类型的对象都可以在不破坏程序的情况下被S类型替换。(先前章节中有讲过)
在调用子类构造方法之前会先调用父类的无参构造器,其目的是?
帮助子类做初始化工作。
一个类的构造方法作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
构造方法的
主要作用是完成对类对象的初始化工作。
可以执行。因为一个类即使没有声明构造方法,也会有默认不带参的构造方法。
构造方法有哪些特性?
1.名字与类名相同。
2.没有返回值(但不能用void声明构造函数)。
3.生成类对象时自动执行,无需调用。
示例如下:
public class Person {
private String name;
private int age;
// 带有两个参数的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 无参构造方法,给成员变量赋默认值
public Person() {
this.name = "匿名";
this.age = 0;
}
}
静态变量和实例变量区别?
静态变量: 静态变量由于不属于任何实例对象,所以在内存中只会有一份,其在类加
载的过程中,JVM只为其分配一次内存空间。
实例变量: 实例变量属于实例对象。每次创建对象,都会对象分配成员变量内存空间。 在内存中,创建几次对象,就有几份成员变量。
静态变量与普通变量区别?
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面:
1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方 式。而调用实例方法只可以用后者。也就是说,调用静态方法可以无需创建对象。
2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而 不允许访问实例成员变量和实例方法;实例方法则无此限制。
在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
什么是方法的返回值?返回值的作用是什么?
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果。(前提是该方法可能产生结果)。
返回值的作用:接收输出结果,使得它可以用于其他的操作。
内部类
什么是内部类?
在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类
。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类有哪些?
内部类可以分为四种:
成员内部类、局部内部类、匿名内部类和静态内部类。
静态内部类
定义在类内部的静态类,就是静态内部类。
示例代码:
public class Outer{
private static int radius =1;
//静态内部类
static class StaticInner{
public void visit(){
System.out.println("visit outer static variable:"+ radius);
}
}
}
静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;
静态内部类的创建方式,
new 外部类.静态内部类()
,如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
成员内部类
定义在类内部,成员位置上的非静态类,就是成员内部类。
public class Outer{
private static int radius =1;
private int count =2;
class Inner{
public void visit(){
System.out.println("visit outer static variable:"+ radius);
System.out.println("visit outer variable:"+ count);
}
}
}
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式
外部类实例.new 内部类()
,如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
局部内部类
定义在方法中的内部类,就是局部内部类。
示例代码如下:
public class Outer{
private int out_a = 1;
private static int STATIC_b = 2;
public void test FunctionClass(){
int inner_c = 3;
classInner{
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void test StaticFunctionClass(){
int d = 3;
class Inner{
private void fun(){
// System.out.println(out_a); 编译错误
//原因:定义在静态方法中的局部内部类不可以访问外部类的实例变量
System.out.println(STATIC_b);
System.out.println(d);}} Inner inner =new Inner();
inner.fun();
}
}
定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
局部内部类的创建方式,在对应方法内,
new 内部类()
,如下:
public static void test StaticFunctionClass(){
class Inner{}
Inner inner =new Inner();
}
匿名内部类
匿名内部类就是没有名字的内部类,日常开发中使用较多.
示例代码如下:
public class Outer{
private void test(final int i){
new Service(){
public void method(){
for(int j =0; j < i; j++){
System.out.println("匿名内部类");
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{void method();}
除了没有名字,匿名内部类还有以下特点:
1.匿名内部类必须继承一个抽象类或者实现一个接口。
2.匿名内部类不能定义任何静态成员和静态方法。
3.当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
4.匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
内部类的优点?
1.一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
2.内部类不为同一包的其他类所见,具有很好的封装性;内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
3.匿名内部类可以很方便的定义回调函数(即钩子函数)。
内部类有哪些应用场景?
1. 一些多算法场合。
2. 解决一些非面向对象的语句块。
3. 适当使用内部类,使得代码更加灵活和富有扩展性。
4. 当某个类除了它的外部类,不再被其他的类使用时。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
先看这段代码:
public class Outer{
void out Method(){
final int a =10;
class Inner{
void inner Method(){
System.out.println(a);
}
}
}
}
以上例子,为什么要加final呢?是因为
生命周期不一致
。局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁,而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加上final,改变了变量的生命周期,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。