系列文章目录
文章目录
1. Java为什么不直接实现Iterator接口,而是实现Iterable
6. Iterator和ListIterator的区别是什么?重点
7. 简述快速失败(fail-fast)和安全失败(fail-safe)的区别 ?
8. hashCode()和equals()方法的重要性体现在什么地方?重点
9. finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
10. Java中Exception和Error有什么区别?
11. 简述异常处理的时候,finally代码块的重要性是什么?
12. Java异常处理完成以后,Exception对象会发生什么变化?
2. 为什么集合类没有实现Cloneable和Serializable接口?重点
5. "static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?重点
6. 解释是否可以在static环境中访问非static变量?重点
7. Java中的方法覆盖(Overriding)和方法重载(Overloading)的区别?重点
8. Java中什么是构造函数?什么是构造函数重载?什么是复制构造函数?
1. 简述什么是Java优先级队列(Priority Queue)?重点
前言
本文主要讲解Java基础知识
一 Java语言
1. Java为什么不直接实现Iterator接口,而是实现Iterable
Iterator是迭代器类,而Iterable是接口。好多类都实现了Iterable接口,这样对象就可以调用iterator()方法。
看一下JDK中的集合类,比如List一族或者Set一族,都是实现了Iterable接口,但并不直接实现Iterator接口。仔细想一下这么做是有道理的。
因为Iterator接口的核心方法next()或者hasNext() 是依赖于迭代器的当前迭代位置的。如果Collection直接实现Iterator接口,势必导致集合对象中包含当前迭代位置的数据(指针)。
当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。
除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。
但即时这样,Collection也只能同时存在一个当前迭代位置。而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器。多个迭代器是互不干扰的。
2. 解释为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程,Java源文件被编译成能被Java虚拟机执行的字节码文件。
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
跨平台原理:
不同的操作系统,有自己专门识别的软件格式.比如Windows系统只识别.exe的.编写的Java程序就没有办法直接在Window当中运行.如果只编写一个Java程序是无法办法在各个系统上运行.
Java单独开发了一套(各个平台)一个Java虚拟机的程序,编写的Java程序不需要运行在系统当中,而是运行在系统安装的Java虚拟机当中.各个平台上的Java虚拟机都能识别编写的Java程序.
在每一个平台当中,都安装了一个JVM软件. 我们编写的Java程序全部都运行在JVM软件当中,这样就可以办到写一份程序在各个平台都可以直接运行啦!
3. 请描述JDK和JRE的区别 ?
JDK:全称Java Development Kit,翻译为Java开发工具包,提供Java的开发和运行环境,是整个Java的核心。目前各大主流公司都有自己的jdk,比如oracle jdk(注意,生产环境使用时需要注意法律风险)、openjdk(目前生产环境主流的jdk)、dragonwell(阿里家的jdk,在金融电商物流方面做了优化)、zulujdk(巨硬家的jdk)等等
JRE:全称Java Runtime Environment,Java运行时环境,为Java提供运行所需的环境
总的来说,JDK包含JRE,JAVA源码的编译器javac,监控工具jconsole,分析工具jvisualvm
总结,如果你需要运行Java程序(类似我的世界那种),只需要安装JRE;如果你需要程序开发,那么需要安装JDK就行了,不需要再重复安装JRE。
4. 简述什么是值传递和引用传递?重点
1.值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
2.引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
5. 简述什么是迭代器(Iterator)?
迭代器(Iterator)是一种设计模式,Java 中的迭代器是集合框架中的一个接口,它可以让程序员遍历集合中的元素而无需暴露集合的内部结构。使用迭代器可以遍历任何类型的集合,例如 List、Set 和 Map 等。
通过调用集合类的 iterator() 方法可以获取一个迭代器,并使用 hasNext() 方法判断是否还有下一个元素,如果有,则使用 next() 方法获取下一个元素。使用迭代器的好处在于遍历集合时不需要了解集合内部的结构,从而让代码更具可维护性和可重用性。
迭代器还具有一些额外的功能,比如支持 remove() 方法来删除迭代器返回的最后一个元素,以及可以使用 forEachRemaining() 方法来迭代集合中余下的所有元素等。
需要注意的是,一旦使用了迭代器进行遍历,就不能在遍历时修改集合中的元素,否则可能会导致不可预知的行为。如果需要修改集合中的元素,应该使用集合提供的遍历方式(如 for-each 循环)来进行遍历,或者使用列表迭代器(ListIterator)来对列表进行修改。
6. Iterator和ListIterator的区别是什么?重点
Iterator 和 ListIterator 都是 Java 集合框架中的迭代器,其中 Iterator 是普遍适用于所有实现了 Iterable 接口的集合类的通用迭代器,而 ListIterator 则是专门用于遍历 List 集合的迭代器,它比 Iterator 更加强大,而且只适用于 List 集合。
以下是 Iterator 和 ListIterator 的主要区别:
1. 支持遍历方向不同:Iterator 只支持从前向后遍历集合,而 ListIterator 支持从前向后遍历和从后向前遍历两个方向。
2. 支持修改元素的方法不同:Iterator 只支持使用 remove() 方法删除集合中的元素,不支持修改和添加操作;而 ListIterator 则支持使用 set() 修改当前元素,以及使用 add() 方法在当前元素之前添加元素。
3. 支持元素索引不同:Iterator 没有提供获取元素索引的方法,而 ListIterator 可以通过 nextIndex() 和 previousIndex() 方法获取下一个元素和上一个元素的索引值。
如果只需要从前向后遍历集合,而且只需要删除元素,那么可以使用更简单的 Iterator 接口;如果需要在遍历时修改或添加元素,或者需要从后向前遍历,则应该使用 ListIterator 接口。
7. 简述快速失败(fail-fast)和安全失败(fail-safe)的区别 ?
快速失败(fail-fast):
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
安全失败(fail-safe):
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
8. hashCode()和equals()方法的重要性体现在什么地方?重点
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,
因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。"
9. finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的 finalize() 方法 但是在 Java 中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说 filalize() 可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么finalize() 究竟是做什么的呢? 它最主要的用途是回收特殊渠道申请的内存。Java 程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种 JNI(Java Native Interface)调用non-Java 程序(C 或 C++), finalize() 的工作就是回收这部分的内存。
10. Java中Exception和Error有什么区别?
Java中,所有的异常都有一个共同的祖先java.lang包中的Throwable类。Throwable类有两个重要的子类Exception(异常)和Error(错误)。
Exception和Error二者都是Java异常处理的重要子类,各自都包含大量子类。
Error:Error属于程序无法处理的错误,是JVM需要负担的责任,无法通过try-catch来进行捕获。例如,系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复。
比如StackOverFlowError;VirtualMachineError;OutofMemoryError;ThreadDeath
Exception:程序本身可以处理的异常,可以通过catch来进行捕获,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。Exception又可以分为运行时异常(RuntimeException,又叫非受检查异常unchecked Exception)和非运行时异常(又叫受检查异常checked Exception)。
checked Exception就是在写代码的时候,需要写try catch的Exception,这种Exception一般不会影响主体程序,可以由程序员手动诊断修复异常。
比如IOException;SQLException;ClassNotFoundException
unchecked Exception又称RunTimeException,这一类就是在代码处理了checked Exception之后,运行时候仍然会遇到的Exception。
例如:NullPropagationException;ClassCastException;ArithmeticException;IllegalArgumentException;IndexOutOfBoundsException(包括ArrayIndexOutofBoundsExcpetion;StringIndexOutofBoundsExcpetion);NumberFormatException。
11. 简述异常处理的时候,finally代码块的重要性是什么?
无论是否抛出异常,finally代码块总是会被执行。就算是没有catch语句同时又抛出异常的情况下,finally代码块仍然会被执行。最后要说的是,finally代码块主要用来释放资源,比如:I/O缓冲区,数据库连接。
12. Java异常处理完成以后,Exception对象会发生什么变化?
Exception对象会在下一个垃圾回收过程中被回收掉
二 Java集合
1. Java集合类框架的基本接口有哪些?
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。
Java集合类里面最基本的接口有:
1 Collection:代表一组对象,每一个对象都是它的子元素。
2 Set:不包含重复元素的Collection。
3 List:有顺序的collection,并且可以包含重复元素。
4 Map:可以把键(key)映射到值(value)的对象,键不能重复。"
2. 为什么集合类没有实现Cloneable和Serializable接口?重点
1.Cloneable接口作用是将一个对象的属性值复制给另一个对象,而不是对象的一个引用。
2.Serializable接口作用(这个罗嗦一下)
#序列化的用途
1.有时候,如果想让一个对象持久的存储下来(存到磁盘),或者是进行远程的对象调用,那就要使用序列化实现这些作用。我们必须对所有支持持久化存储的类实现Serializable接口,读取的时候也要进行反序列化。
2.对于jvm来说,进行持久化的类必须有个标记,就是实现Serializable接口,关联serialVersionUID,这个变量就是在反序列话中确定用那个类加载这个对象。
3.值得主意的是,持久化的数据都是存在在java堆中,static类型的数据存在在方法区中,不能被持久化。如果不想让某个成员变量持久化,变量前面用transient关键字
4.当然序列化的那个serialVersionUID这个还可以进行自定义
3.回到主题,为什么集合类中不实现上面两个接口呢
其实不难看出,Cloneable是复制对象的,序列化也是针对对象的操作,集合类只是管理对象的一个工具,就好比说list能够线性的管理对象,set集合能够对对象去重等,这些集合类都是针对与为管理对象而产生的。
其实,着两个接口都是针对真是的对象,而不是集合类这样的管理对象的对象。这个从语义上就是集合类的Cloneable接口和Serializable接口
应该又集合中具体的类型实现,而不是又集合类来实现序列化。
假设集合类实现了这两个接口,如果我要生成一个不需要序列化,不需要clone的集合,那么集合类就强行实现,这样有违集合的设计原则。
Java中的HashMap的工作原理是什么?
Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,
然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。"
3. HashMap和Hashtable有什么区别?
1 HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
2 HashMap允许键和值是null,而Hashtable不允许键或者值是null。
3 Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
4 HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
5 一般认为Hashtable是一个遗留的类。"
4. Java如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。
有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。"
三 Java面向对象
1. Java中的 组合、聚合和关联有什么区别?重点
如果两个对象彼此有关系,就说他们是彼此相关联的。组合和聚合是面向对象中的两种形式的关联。组合是一种比聚合更强力的关联。组合中,一个对象是另一个的拥有者,而聚合则是指一个对象使用另一个对象。如果对象 A 是由对象 B 组合的,则 A 不存在的话,B一定不存在,但是如果 A 对象聚合了一个对象 B,则即使 A 不存在了,B 也可以单独存在。
2. 请设计一个符合开闭原则的设计模式的例子?重点
开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增加一个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代码。有好几个设计模式是基于开闭原则的,如策略模式,如果你需要一个新的策略,只需要实现接口,增加配置,不需要改变核心逻辑。一个正在工作的例子是 Collections.sort() 方法,这就是基于策略模式,遵循开闭原则的,你不需为新的对象修改 sort() 方法,你需要做的仅仅是实现你自己的 Comparator 接口。
3. Java面向对象编程(OOP)相关解释 ?重点
Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点:
1 代码开发模块化,更易维护和修改。
2 代码复用。
3 增强代码的可靠性和灵活性。
4 增加代码的可理解性。
面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。下面的章节我们会逐个分析这些特性。
封装
封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有3种修饰符:public,private和protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。
下面列出了使用封装的一些好处:
1 通过隐藏对象的属性来保护对象内部的状态。
2 提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。
3 禁止对象之间的不良交互提高模块化。
参考这个文档获取更多关于封装的细节和示例。
多态:多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作可以应用到其他类型的值上面。
继承:继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。
抽象:抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。"
补充OOP的相关知识
面向对象编程(Object-Oriented Programming)与面向过程(Procedure Oriented )
- 两种方法都是编程中的比较常用的方法,从理论上来说,都能达到用计算机程序来解决实际问题的目的, 只不过是其中所体现出来的思想不一样而已。
- 面向过程:面向过程的思想是把一个项目、一件事情按照一定的顺序,从头到尾一步一步地做下去,先做什么,后做什么,一直到结束。这种思想比较好理解,其实这也是一个人做事的方法。
- 面向对象:面向对象的思想是把一个项目、一件事情分成更小的项目,或者说分成一个个更小的部分,每一部分负责什么方面的功能,最后再由这些部分组合而成为一个整体。这种思想比较适合多人的分工合作,就像一个大的机关,分成各个部门,每个部门分别负责某样职能,各个部门可以充分发挥自己的特色,只要符合一定前提就行了。
面向对象(OOP)的三个特征:
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
一、封装
1、定义:
- Encapsulation in Java is a mechanism of wrapping the data (variables) and code acting on the data (methods) together as a single unit. In encapsulation, the variables of a class will be hidden from other classes, and can be accessed only through the methods of their current class. Java中的封装是一种将数据(变量)和作用于数据(方法)的代码打包为一个单元的机制。在封装中,类的变量将对其他类隐藏,并且只能通过当前类的方法访问。
- Encapsulation can change the internal structure of a class without affecting the overall structure, while protecting the data. For the outside world, its interior is hidden, and what is exposed to the outside world is only the methods that can access it. 封装可以对类的内部进行改变而不影响整体结构,同时也保护来数据。对于外界而言,它的内部是隐藏的,暴露给外界的只是可以访问它的方法。
2、优点:
- 类内部可以自由修改
- 可以对成员变量更准确的控制
- 隐藏信息,保护数据
- 降低耦合度
3、Java的访问控制级别
(1)public:对外公开,级别最高。
(2)protect:只对同一个包中的类或子类公开,级别次之。
(3)默认:只对同一个包中的类公开
(4)private:不对外公开,只能在对象内部访问,级别最低。
例子:
class Demo { private String name; private String idnum; private int age; public String getname(){ return name; } public void setname(String name){ this.name=name; } public String getidnum(){ return idnum; } public void setidnum(String idnum){ this.idnum=idnum; } public int getage(){ return age; } public void setage(int age){ this.age=age; } } public class Demo{ public static void main(String[] args) { Demo demo = new Demo(); demo.setage(18); demo.setname("张三"); demo.setidnum("123456"); System.out.println("名字为" + demo.getname() + ",年龄为" + demo.getage() + ",学号为" + demo.getidnum()); } }
二、继承
public class C extends A{ ... }
1、使用extends关键字。继承允许class继承另一个class的一些通用的fields和methods。多个类存在相同的属性和行为时,定义这么多个类的属性和行为属实有些麻烦,将他们放到单独一个类中就相对比较简洁,只需继承那个定义好属性和行为那个类即可。
- super class :父类。是哪个可以被别人继承自己的属性的class。
- sub class :子类。是继承另一个class的属性的class。子类也可以添加自己的fields和methods,也可以覆盖重写(override)父类的方法(不影响父类)。
2、作用:
- 继承提高了代码的重复性
- 继承的出现让类与类之间产生了关系,为多太提供了条件。
3、Java只支持单继承,不允许多继承,即一个子类只能有一个父类,而父类可以有多个子类。
重载(overload)
public class Overloading { public int test(){ System.out.println("test1"); return 1; } public void test(int a){ System.out.println("test2"); } //以下两个参数类型顺序不同 public String test(int a,String s){ System.out.println("test3"); return "returntest3"; } public String test(String s,int a){ System.out.println("test4"); return "returntest4"; } public static void main(String[] args){ Overloading o = new Overloading(); System.out.println(o.test()); o.test(1); System.out.println(o.test(1,"test3")); System.out.println(o.test("test4",1)); } }
重载方法满足的一些条件:
(1)方法名相同。
(2)方法的参数类型、个数、顺序至少有一项不同。
(3)方法的返回值可以不同。
(4)方法的修饰符可以不同。
方法重写/覆盖(override):
class Animal{ public void greeting() { System.out.println("Hi, I am a Animal"); } } class Cat extends Animal { @Override public void greeting() { System.out.println("Mome, I am a Cat"); } } public class Main { public static void main(String args[]) { Animal an = new Animal(); an.greeting(); //Hi, I am a Animal Cat cat = new Cat(); cat.greeting(); //Mome, I am a Cat //顺便看一下runtime 类型的意思 instanceof时提到的 Animal an2 = new Cat(); //虽然an2是个animal,但是被创建的时候是用Cat class,所以调用cat里的方法 an2.greeting();//Mome, I am a Cat } }
覆盖(override)的一些规则:
- method只能在子类里面继承,不能在同一个class里面。
- 子类参数和父类参数一定要一模一样。谁都不能是谁的subclass
- 子类的返回类型要和父类的返回类型一样,或者是父类的返回类型的子类。
- access level 只能越来越大。如果父类是public的,子类只能是public的。如果父类是protected,那子类只能是protected或者public。
- private的方法不能被override。
- 一个加了final的method不能被override。
- 一个加了static的method也不能被overrride。但是可以被重新定义。因为static是class level的。跟子类object没关系。
- 如果子类和父类在同一个包里,子类可以overrride父类的不是private和final的方法。
- 如果子类和父类不在同一个包里,那子类只可以overrride父类public 或者 protected的非final方法。
- 子类可以随意抛出uncheck exceptions,不管父类有没有抛出异常。对于checked exception, 子类抛出的异常需要是父类抛出异常的孩子,或者是抛出比父类少的异常。(narrower or fewer)
- Constructor 不能被 overridde.
重载(overload)与覆盖(override)的区别
总结:
- (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
三、多态
1、多态是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
2、在Java中有两种体现:
- 方法重载(overload)和覆盖(override)。
- 对象的多态性——可以直接应用在抽象类和接口上。
3、多态存在的三个前提:
- 要有继承关系
- 子类要重写父类的方法
- 父类引用指向子类对象
4、当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
5、多态的定义与使用格式
定义格式:父类类型 变量名=new 子类类型();
多态成员的特点:
(1)多态成员方法:编译看左边,运行看右边
Fu f1=new Zi(); System.out.println(f1.show()); //f1的门面类型是Fu,但实际类型是Zi,所以调用的是重写后的方法
6、instanceof关键字
x instanceof A :检验x是否为类A的对象,返回值为boolean型。
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true。
多态案例:
package day0524; public class demo04 { public static void main(String[] args) { People p=new Stu(); p.eat(); //调用特有的方法 Stu s=(Stu)p; s.study(); //((Stu) p).study(); } } class People{ public void eat(){ System.out.println("吃饭"); } } class Stu extends People{ @Override public void eat(){ System.out.println("吃水煮肉片"); } public void study(){ System.out.println("好好学习"); } } class Teachers extends People{ @Override public void eat(){ System.out.println("吃樱桃"); } public void teach(){ System.out.println("认真授课"); }
4. 阐述Java抽象和封装的不同点?
抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。
有一句名言:“软件领域的任何问题,都可以通过增加一个间接的中间层来解决”。分层架构的核心其实就是抽象的分层,每一层的抽象只需要而且只能关注本层相关的信息,从而简化整个系统的设计。
设计与分析的过程就是不听的抽象和封装,并且确定各个系统实体的细节。
抽象是指将业务抽象为软件领域的元素(系统,模块或类);封装则是指定义元素的边界,隐藏实现,开放接口。
抽象是指从众多的事物中抽取出具体共同的,本质性的特征作为一个整体。是共同特质的集合形式。
封装是指从众多的事物中抽取出具有共同的,本质性的特征作为一个整体。是共同特质的集合形式。
封装是指通过抽象所得到的数据信息和操作进行结合,使其形成一个有机的整体。对内执行操作,对外隐藏细节和数据信息。
两者的区别在于抽象是一种思维方式,而封装则是一种基于抽象的操作方法。我们通过抽象所得到数据信息及其功能,以封装的技术将其重新聚合,形成一个新的聚合体,也就是两者是合作者的关系。如果没有抽象,封装就无从谈起;如果没有封装;抽象也将没有意义
5. "static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?重点
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。"
6. 解释是否可以在static环境中访问非static变量?重点
答案是不可以,因为static变量是属于类的,在类加载的时候就被初始化了,这时候非静态变量并没有加载,故非静态变量不能访问。
这个要从java的内存机制去分析,首先当你New 一个对象的时候,并不是先在堆中为对象开辟内存空间,而是先将类中的静态方法(带有static修饰的静态函数)的代码加载到一个叫做方法区的地方,然后再在堆内存中创建对象。所以说静态方法会随着类的加载而被加载。当你new一个对象时,该对象存在于对内存中,this关键字一般指该对象,但是如果没有new对象,而是通过类名调用该类的静态方法也可以。
程序最终都是在内存中执行,变量只有在内存中占有一席之地时才会被访问,类的静态成员(变态和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问,非静态成员(变量和方法)属于类的对象,所以只有在类的对象禅师(创建实例)的时候才会分配内存,然后通过类的对象去访问。
在一个类的静态成员中去访问非静态成员之所以会出错是因为在类的非静态成员不存在的时候静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
那类是什么时候被加载呢?在需要调用的时候被加载
7. Java中的方法覆盖(Overriding)和方法重载(Overloading)的区别?重点
java中的方法重载发生在同一个类里面两个或者多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。
重载
(1)方法重载是让类以统一的方法处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数(类型)。重载Override是一个类中多态性的一种表现。
(2)java的方法重载,就是在类中可以创建多个方法,他们具有相同的名字,但具有不同参数和不同的定义。调用方法时通过传递给他们不同的参数个数和参数类型来决定具体使用那个方法,这就是多态性。
(3)重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不同。无法以返回类型来作为重载函数的区分标准。
重写(覆盖)
(1)父类与子类的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写。在java中,子类可继承父类的方法,则不需要重新编写相同的方法。但有时子类并不想原封不动继承父类的方法,而是想做一定的修改,这就采用方法重写。方法重写又称方法覆盖。
(2)若子类中的方法与父类的中的某一方法具有相同的方法名、返回类型和参数表,则新方法覆盖原有的方法。如需要父类的原有方法,可以使用super关键字,该关键字引用房钱类的父类。
(3)子类函数访问权限大于父类
8. Java中什么是构造函数?什么是构造函数重载?什么是复制构造函数?
当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。
Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。"
四 Java算法原理
1. Java常规排序有哪些分类?重点
排序可以分为内部排序和外部排序,在内存中进⾏的称为内部排序,当数据量很⼤时⽆法全部拷⻉到内 存需要使⽤外存,称为外部排序。
内部排序包括⽐较排序和⾮⽐较排序,⽐较排序包括插⼊/选择/交换/归并排序,⾮⽐较排序包括计数/ 基数/桶排序。
插⼊排序包括直接插⼊/希尔排序,选择排序包括直接选择/堆排序,交换排序包括冒泡/快速排序
2. 简述直接插入排序的原理 ?重点
插入排序(Insertion sort)是一种简单直观且稳定的排序算法。
排序原理:
1.把所有的元素分为两组,已经排序的和未排序的;
2.找到未排序的组中的第一个元素,向已经排序的组中进行插入;
3.倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待
插入元素放到这个位置,其他的元素向后移动一位;
2.交换第一个索引处和最小值所在的索引处的值
插入排序的时间复杂度分析
插入排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以,我们分析插入排序的时间复杂度,主要分析一下内层循环体的执行次数即可。
最坏情况,也就是待排序的数组元素为{12,10,6,5,4,3,2,1},那么:
比较的次数为:(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
交换的次数为:(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
总执行次数为:(N2/2-N/2)+(N2/2-N/2)=N^2-N;
按照大O推导法则,保留函数中的最高阶项那么最终插入排序的时间复杂度为O(N^2)
3. 编写Java代码实现直接插入排序 ?重点
// 插入排序
public class Insertion {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for (int i = 1; i < a.length; i++) {
for (int j = i; j > 0; j--) {
//比较索引j处的值和索引j-1处的值,如果索引j-1处的值比索引j处的值大,则交换数据,如果不大,那么就找到合适的位置了,退出循环即可;
if (greater(a[j - 1], a[j])) {
exch(a, j - 1, j);
} else {
break;
}
}
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
// 测试代码
public class InsertionTest {
public static void main(String[] args) {
Integer[] a = {
4, 3, 2, 10, 12, 1, 5, 6
}
;
Insertion.sort(a);
System.out.println(Arrays.toString(a));
//{1,2,3,4,5,6,10,12}
}
}
4. 简述冒泡排序的原理 ?重点
冒泡排序:(Bubble Sort)是一种简单的交换排序。之所以叫做冒泡排序,因为我们可以把每个元素当成一个小气泡,根据气泡大小,一步一步移动到队伍的一端,最后形成一定对的顺序。
冒泡排序的原理:
我们以一个队伍站队为例,教官第一次给队员排队是无序的,这时候就需要排队,按矮到高的顺序排列,首先拎出第一第二个比较,如果第一个队员比第二个要高,则两个交换位置, 高的放到排到第二个位置,矮的就排到第一个,再把第二个,第三个比较,把高的排到后面一个位置,然后以此类推,直至第一轮所有队员都比较过一次(记住每次比较都是相邻的两个),这样就可以把最高的排到最后的位置。
总结就是:每一轮都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
五 Java算法实现
算法编程 1:请编写Java代码实现实现以下逻辑与输出 ?
题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
1.程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21….
public class exp2{
public static void main(String args[]){
int i=0;
for(i=1;i<=20;i++)
System.out.println(f(i));
}
public static int f(int x)
{
if(x==1 || x==2)
return 1;
else
return f(x-1)+f(x-2);
}
}
或
public class exp2{
public static void main(String args[]){
int i=0;
math mymath = new math();
for(i=1;i<=20;i++)
System.out.println(mymath.f(i));
}
}
class math
{
public int f(int x)
{
if(x==1 || x==2)
return 1;
else
return f(x-1)+f(x-2);
}
}
算法编程 2:请编写Java代码实现实现以下逻辑与输出 ?
题目:判断101-200之间有多少个素数,并输出所有素数。
1.程序分析:判断素数的方法:用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。
public class exp2{
public static void main(String args[]){
int i=0;
math mymath = new math();
for(i=2;i<=200;i++)
if(mymath.iszhishu(i)==true)
System.out.println(i);
}
}
class math
{
public int f(int x)
{
if(x==1 || x==2)
return 1;
else
return f(x-1)+f(x-2);
}
public boolean iszhishu(int x)
{
for(int i=2;i<=x/2;i++)
if (x % 2==0 )
return false;
return true;
}
}
算法编程 3:请编写Java代码实现实现以下逻辑与输出 ?
题目:打印出所有的 “水仙花数 “,所谓 “水仙花数 “是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个 “水仙花数 “,因为153=1的三次方+5的三次方+3的三次方
1.程序分析:利用for循环控制100-999个数,每个数分解出个位,十位,百位
public class exp2 {
public static void main(String args[]) {
int i=0;
math mymath = new math();
for (i=100;i<=999;i++)
if(mymath.shuixianhua(i)==true)
System.out.println(i);
}
}
class math {
public int f(int x) {
if(x==1 || x==2)
return 1; else
return f(x-1)+f(x-2);
}
public boolean iszhishu(int x) {
for (int i=2;i<=x/2;i++)
if (x % 2==0 )
return false;
return true;
}
public boolean shuixianhua(int x) {
int i=0,j=0,k=0;
i=x / 100;
j=(x % 100) /10;
k=x % 10;
if(x==i*i*i+j*j*j+k*k*k)
return true; else
return false;
}
}
算法编程 4:请编写Java代码实现实现以下逻辑与输出 ?
题目:将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5。
程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成:
(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。
(2)如果n <> k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你,重复执行第一步。
(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。
public class exp2 {
public exp2() {
}
public void fengjie(int n) {
for (int i=2;i<=n/2;i++) {
if(n%i==0)
System.out.print(i+"*");
fengjie(n/i);
}
}
System.out.print(n);
System.exit(0);
///不能少这句,否则结果会出错
}
public static void main(String[] args) {
String str="";
exp2 c=new exp2();
str=javax.swing.JOptionPane.showInputDialog("请输入N的值(输入exit退出):");
int N;
N=0;
try {
N=Integer.parseInt(str);
}
catch(NumberFormatException e) {
e.printStackTrace();
}
System.out.print(N+"分解质因数:"+N+"=");
c.fengjie(N);
}
}
算法编程 5:请编写Java代码实现实现以下逻辑与输出 ?
题目:利用条件运算符的嵌套来完成此题:学习成绩> =90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示
程序分析:(a> b)a:b这是条件运算符的基本例子。
import javax.swing.*;
public class ex5 {
public static void main(String[] args) {
String str="";
str=JOptionPane.showInputDialog("请输入N的值(输入exit退出):");
int N;
N=0;
try {
N=Integer.parseInt(str);
}
catch(NumberFormatException e) {
e.printStackTrace();
}
str=(N>90?"A":(N>60?"B":"C"));
System.out.println(str);
}
}
算法编程 6:请编写Java代码实现实现以下逻辑与输出 ?
题目:输入两个正整数m和n,求其最大公约数和最小公倍数。
程序分析:利用辗除法。最大公约数:
public class CommonDivisor{
public static void main(String args[])
{
commonDivisor(24,32);
}
static int commonDivisor(int M, int N)
{
if(N<0||M<0)
{
System.out.println("ERROR!");
return -1;
}
if(N==0)
{
System.out.println("the biggest common divisor is :"+M);
return M;
}
return commonDivisor(N,M%N);
}
}
最大公约数和最小公倍数:
import java.util.Scanner;
public class CandC
{
//下面的方法是求出最大公约数
public static int gcd(int m, int n)
{
while (true)
{
if ((m = m % n) == 0)
return n;
if ((n = n % m) == 0)
return m;
}
}
public static void main(String args[]) throws Exception
{
//取得输入值
//Scanner chin = new Scanner(System.in);
//int a = chin.nextInt(), b = chin.nextInt();
int a=23; int b=32;
int c = gcd(a, b);
System.out.println("最小公倍数:" + a * b / c + "
最大公约数:" + c);
}
}
六 Java数据结构
1. 简述什么是Java优先级队列(Priority Queue)?重点
PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。
PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
请描述大O符号(big-O notation)的作用 ?
大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。
大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
2. 简述Java 栈的基本概念 ?
栈是只允许在表尾(栈顶)插入和删除元素的线性表,其操作特点是 先进后出(FILO)或后进先出(LIFO)。栈的实现方式有两种,分别顺序栈和链栈。
顺序栈使用一组地址连续的内存单元来存储栈中的元素,同时设置一个栈顶变量top,用来标识栈顶元素。链栈是用一组任意的存储单元来存储栈中的元素,链栈中的指针从栈顶指向栈底,同时设置一个栈顶变量指向栈中的第一个元素(栈顶元素)
3. 编写Java代码实现顺序栈 ?重点
定义一个ArrayStack类,每一个ArrayStack对象就代表一个栈,并在该类中定义关于顺序栈的基本操作方法
class ArrayStack {
private int maxSize; //栈的最大容量
private int[] stack; //存放栈数据
private int top = -1; //top表示栈顶,初始化为-1
//初始化栈
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//判断栈是否已满
public boolean isFull() {
return this.top == (this.maxSize - 1);
}
//判断栈是否为空
public boolean isEmpty() {
return this.top == -1;
}
//入栈
public void push(int value) {
//判断栈是否已满
if (isFull()) {
throw new RuntimeException("当前栈已满");
}
stack[++top] = value;
}
//出栈,将栈顶的数据返回
public int pop() {
//判断是否为空
if (isEmpty()) {
throw new RuntimeException("栈为空");
}
//返回top对应的值,并让top--
int value = this.top;
this.top--;
return value;
}
//遍历栈, 从栈顶开始遍历
public void list() {
//判断栈是否为空
if (isEmpty()) {
System.out.println("栈为空");
return;
}
for (int i = top; i > -1; i--) {
System.out.println("stack[" + i + "]=" + stack[i]);
}
}
}
在main方法中测试(完整代码)
package cn.hypig.stack;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(5);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
//stack.push(10);栈满
stack.list();
System.out.println("------------------------");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
4. 编写Java代码实现链栈 ?重点
定义栈中的节点类
class ArrayStack {
private int maxSize; //栈的最大容量
private int[] stack; //存放栈数据
private int top = -1; //top表示栈顶,初始化为-1
//初始化栈
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//判断栈是否已满
public boolean isFull() {
return this.top == (this.maxSize - 1);
}
//判断栈是否为空
public boolean isEmpty() {
return this.top == -1;
}
//入栈
public void push(int value) {
//判断栈是否已满
if (isFull()) {
throw new RuntimeException("当前栈已满");
}
stack[++top] = value;
}
//出栈,将栈顶的数据返回
public int pop() {
//判断是否为空
if (isEmpty()) {
throw new RuntimeException("栈为空");
}
//返回top对应的值,并让top--
int value = this.top;
this.top--;
return value;
}
//遍历栈, 从栈顶开始遍历
public void list() {
//判断栈是否为空
if (isEmpty()) {
System.out.println("栈为空");
return;
}
for (int i = top; i > -1; i--) {
System.out.println("stack[" + i + "]=" + stack[i]);
}
}
}
在main方法中测试(完整代码)
package cn.hypig.stack;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(5);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
//stack.push(10);栈满
stack.list();
System.out.println("------------------------");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
编写Java代码实现链栈 ?重点
定义栈中的节点类
//该类表示栈的节点信息
class StackNode {
private int no;
private String name;
private StackNode next; //指向下一个节点
public StackNode(int no, String name) {
this.no = no;
this.name = name;
}
public StackNode() {
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StackNode getNext() {
return next;
}
public void setNext(StackNode next) {
this.next = next;
}
@Override
public String toString() {
return "StackNode{" +
"no=" + no +
", name='" + name + '\'' +
"}";
}
}
定义一个LinkedStack类,每一个LinkedStack对象,就代表一个链栈,并在该类中定义关于栈的基本操作方法
//该类表示栈结构
class LinkedStack {
//定义一个头节点,指向栈中的栈顶元素
private StackNode top = new StackNode();
//返回栈的栈顶变量
public StackNode getTop() {
return this.top;
}
//判断栈是否为空
public boolean isEmpty() {
return this.top.getNext() == null;
}
//入栈
public void push(StackNode node) {
//将栈顶结点top的next赋给新节点
node.setNext(this.top.getNext());
//让top指向新节点
top.setNext(node);
}
//出栈
public StackNode pop() {
//判断栈是否为空
if (isEmpty()) {
throw new RuntimeException("当前栈为空");
}
//要返回的节点
StackNode popNode = top.getNext();
//让top的next指向返回节点的next
top.setNext(popNode.getNext());
return popNode;
}
//遍历栈
public void list() {
//判断栈是否为空
if (isEmpty()) {
throw new RuntimeException("当前栈为空");
}
//定义一个辅助变量 temp 来接收top,辅助遍历
StackNode temp = top.getNext();
//开始遍历
while (true) {
if (temp == null) {
//遍历结束
break;
}
System.out.println(temp);
temp = temp.getNext();
}
}
}
在main方法中测试
public class LinkedStackDemo {
public static void main(String[] args) {
StackNode node1 = new StackNode(1, "张三");
StackNode node2 = new StackNode(2, "李四");
StackNode node3 = new StackNode(3, "王五");
StackNode node4 = new StackNode(4, "赵六");
LinkedStack linkedStack = new LinkedStack();
linkedStack.push(node1);
linkedStack.push(node2);
linkedStack.push(node3);
linkedStack.push(node4);
linkedStack.list();
System.out.println("-------------------------");
System.out.println(linkedStack.pop());
System.out.println(linkedStack.pop());
System.out.println(linkedStack.pop());
System.out.println(linkedStack.pop());
System.out.println(linkedStack.pop());
}
}
总结
以上就是今天的内容~
欢迎大家点赞👍,收藏⭐,转发🚀,
如有问题、建议,请您在评论区留言💬哦。
最后:转载请注明出处!!!