面向对象的三个基本特征?
面向对象的三个基本特征是:封装、继承和多态。
封装:隐藏部分对象的属性和实现细节,对数据的访问只能通过外公开的接口。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承:让某个类型的对象获得另一个类型的对象的属性的方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
多态:对于同一个行为,不同的子类对象具有不同的表现形式。多态存在的3个条件:1)继承;2)重写;3)父类引用指向子类对象。
举个简单的例子:动物都有睡觉这一个行为
- 狗睡觉,是趴着睡
- 鸡睡觉,是站着睡
- 猫睡觉,是四脚朝天睡
同一个事件发生在不同的对象上会产生不同的结果。
String 类可以继承吗?
不行。String 类使用 final 修饰,无法被继承。
String和StringBuilder、StringBuffer的区别?
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
StringBuilder:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。
== 和 equals 的区别是什么?
==:运算符,用于比较基础类型变量和引用类型变量。
对于基础类型变量,比较的变量保存的值是否相同,类型不一定要相同。
short s1 = 1; long l1 = 1;
// 结果:true。类型不同,但是值相同
System.out.println(s1 == l1);
对于引用类型变量,比较的是两个对象的地址是否相同。
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// 结果:false。通过new创建,在内存中指向两个不同的对象
System.out.println(i1 == i2);
equals:Object 类中定义的方法,通常用于比较两个对象的值是否相等。
两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
不对。hashCode() 和 equals() 之间的关系如下:
当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,
反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。
什么是反射
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。
构造器是否可被 重写?
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到?个类中有多个构造函数的情况。(无参构造、有参构造)
Java 静态变量和成员变量的区别。
public class Demo {
/**
* 静态变量:又称类变量,static修饰
*/
public static String STATIC_VARIABLE = "静态变量";
/**
* 实例变量:又称成员变量,没有static修饰
*/
public String INSTANCE_VARIABLE = "实例变量";
}
成员变量存在于堆内存中。静态变量存在于方法区中。
成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。
成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。
成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。
重载(Overload)和重写(Override)的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。
重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。
抽象类(abstract class)和接口(interface)有什么区别?
抽象类只能单继承,接口可以多实现。 抽象类可以有构造方法,接口中不能有构造方法。 抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final) 抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。 抽象类中的抽象方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型
设计思想的区别: 接口是自上而下的抽象过程,接口规范了某些行为,是对某一行为的抽象。我需要这个行为,我就去实现某个接口,但是具体这个行为怎么实现,完全由自己决定。
抽象类是自下而上的抽象过程,抽象类提供了通用实现,是对某一类事物的抽象。我们在写实现类的时候,发现某些实现类具有几乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有一些差异点,则可以提供抽象方法来支持自定义实现。
我在网上看到有个说法,挺形象的:
普通类像亲爹 ,他有啥都是你的。 抽象类像叔伯,有一部分会给你,还能指导你做事的方法。 接口像干爹,可以给你指引方法,但是做成啥样得你自己努力实现。
Java 中的 final 关键字有哪些用法?
修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final。
修饰方法:该方法不能被子类重写。
修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的。
public class FinalDemo {
// 不可再修改该变量的值
public static final int FINAL_VARIABLE = 0;
// 不可再修改该变量的引用,但是可以直接修改属性值
public static final User USER = new User();
public static void main(String[] args) {
// 输出:User(id=0, name=null, age=0)
System.out.println(USER);
// 直接修改属性值
USER.setName("test");
// 输出:User(id=0, name=test, age=0)
System.out.println(USER);
}
}
Java集合框架
List 和 Set,Map 的区别
- List 以索引来存取元素,有序的,元素是允许重复的,可以插入多个null。
- Set 不能存放重复元素,无序的,只允许一个null
- Map 保存键值对映射,映射关系可以一对一、多对一
- List 有基于数组、链表实现两种方式
- Set、Map 容器有基于哈希存储和红黑树两种方式实现
- Set 基于 Map 实现,Set 里的元素值就是 Map的键值
Arraylist与LinkedList区别
- ArrayList是数组的数据结构,LinkedList是链表的数据结构。
- 随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。
- 插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。
- LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
HashMap,HashTable的共同点和区别
HashMap
- 底层由链表+数组+红黑树实现
- 可以存储null键和null值
- 线性不安全
- 初始容量为16,扩容每次都是2的n次幂
- 加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作.
- 并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
- HashMap是对Map接口的实现
HashTable
- HashTable的底层也是由链表+数组+红黑树实现。
- 无论key还是value都不能为null
- 它是线性安全的,使用了synchronized关键字。
- HashTable实现了Map接口和Dictionary抽象类
- Hashtable初始容量为11
JDK1.8之后有哪些新特性?
接口默认方法:Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可
Lambda 表达式和函数式接口:Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中),使用 Lambda 表达式使代码更加简洁,但是也不要滥用,否则会有可读性等问题,《Effective Java》作者 Josh Bloch 建议使用 Lambda 表达式最好不要超过3行。
Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合Lambda表达式可以方便的对集合进行处理。Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
日期时间API:Java 8 引入了新的日期时间API改进了日期时间的管理。
Optional 类:著名的 NullPointerException 是引起系统失败最常见的原因。很久以前 Google Guava 项目引入了 Optional 作为解决空指针异常的一种方式,不赞成代码被 null 检查的代码污染,期望程序员写整洁的代码。受Google Guava的鼓励,Optional 现在是Java 8库的一部分。
新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器 jdeps。
JDK中几个常用的设计模式?
- 单例模式:保证被创建一次,节省系统开销。
- 工厂模式(简单工厂、抽象工厂):解耦代码。
- 观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
- 外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。
- 模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。
- 状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
- 装饰器设计模式:(Decorator design pattern)被用于多个 Java IO 类中。
什么是单例模式?
单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。
单例模式代码实现:
class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
单例模式调用代码:
public class Lesson7_3 {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2);
}
}
什么是简单工厂模式?
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可。
优点:
- 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象;
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
- 不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
- 产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。
什么是抽象工厂模式??
抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定。
比如,以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。这个时候就可以使用抽象工厂类了,抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。
抽象工厂实现代码如下:
public class AbstractFactoryTest {
public static void main(String[] args) {
// 抽象工厂
String result = (new CoffeeFactory()).createProduct("Latte");
System.out.println(result); // output:拿铁
}
}
// 抽象工厂
abstract class AbstractFactory{
public abstract String createProduct(String product);
}
// 啤酒工厂
class BeerFactory extends AbstractFactory{
@Override
public String createProduct(String product) {
String result = null;
switch (product) {
case "Hans":
result = "汉斯";
break;
case "Yanjing":
result = "燕京";
break;
default:
result = "其他啤酒";
break;
}
return result;
}
}
/*
* 咖啡工厂
*/
class CoffeeFactory extends AbstractFactory{
@Override
public String createProduct(String product) {
String result = null;
switch (product) {
case "Mocca":
result = "摩卡";
break;
case "Latte":
result = "拿铁";
break;
default:
result = "其他咖啡";
break;
}
return result;
}
}
简单工厂和抽象工厂有什么区别?
- 简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。
- 工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
- 抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。
为什么索引能够提高查询速度?
索引就是通过事先排好序,从而在查找时可以应用二分查找等高效率的算法。 一般的顺序查找,复杂度为O(n),而二分查找复杂度为O(log2n)。当n很大时,二者的效率相差及其悬殊。
举个例子: 表中有一百万条数据,需要在其中寻找一条特定id的数据。如果顺序查找,平均需要查找50万条数据。而用二分法,至多不超过20次就能找到。二者的效率差了2.5万倍!
|