JAVA学习笔记
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了JAVA学习笔记,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含22372字,纯文字阅读大概需要32分钟。
内容图文
IDEA编译:
在 IntelliJ IDEA 里,编译方式一共有三种:
? Compile:对选定的目标(Java 类文件),进行强制性编译,不管目标是否是被修改过。
? Rebuild:对选定的目标(Project),进行强制性编译,不管目标是否是被修改过,由于 Rebuild 的目标只有 Project,所以 Rebuild 每次花的时间会比较长。
? Make:使用最多的编译操作。对选定的目标(Project 或 Module)进行编译,但只编译有修改过的文件,没有修改过的文件不会编译,这样平时开发大型项目才不会浪费时间在编译过程中。
Java对象指针:
JAVA中可以说没有指针,因为实际上我们在程序中不会直接操作地址,C++中的*、->操作在JAVA中都不能正常使用。
JAVA中也可以说到处都是指针,因为实际上我们定义一个对象,并给它初始化的时候,这个定义的类对象实际上就是指针。
JVAA中函数的参数是基本类型和对象类型时,他们的处理是不一样的,基本类型传递的是值,而对象类型传递的是引用(类似于指针)。
注意:编译的结果不是生成机器码,而是生成字节码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样
注意:跨平台的是Java程序,不是JVM。JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM。
Java类库中有很多包:
? 以 java.* 开头的是Java的核心包,所有程序都会使用这些包中的类;
? 以 javax.* 开头的是扩展包,x 是 extension 的意思,也就是扩展。虽然 javax.* 是对 java.* 的优化和扩展,但是由于 javax.* 使用的越来越多,很多程序都依赖于 javax.,所以 javax. 也是核心的一部分了,也随JDK一起发布。
? 以 org.* 开头的是各个机构或组织发布的包,因为这些组织很有影响力,它们的代码质量很高,所以也将它们开发的部分常用的类随JDK一起发布。
组织机构的域名后缀一般为 org,公司的域名后缀一般为 com,可以认为 org.* 开头的包为非盈利组织机构发布的包,它们一般是开源的,可以免费使用在自己的产品中,不用考虑侵权问题,而以 com.* 开头的包往往由盈利性的公司发布,可能会有版权问题,使用时要注意。
import语句与C语言中的 #include 有些类似,语法为:
import package1[.package2…].classname;
package 为包名,classname 为类名。例如:
- import java.util.Date; // 导入 java.util 包下的 Date 类
- import java.util.Scanner; // 导入 java.util 包下的 Scanner 类
- import javax.swing.; // 导入 javax.swing 包下的所有类, 表示所有类
注意:
? import 只能导入包所包含的类,而不能导入包。
? 为方便起见,我们一般不导入单独的类,而是导入包下所有的类,例如 import java.util.*;。
Java类的搜索路径
Java程序运行时要导入相应的类,也就是加载 .class 文件的过程。
假设有如下的 import 语句:
- import p1.Test;
该语句表明要导入 p1 包中的 Test 类。
安装JDK时,我们已经设置了环境变量 CLASSPATH 来指明类库的路径,它的值为 .;%JAVA_HOME%\lib,而 JAVA_HOME 又为 D:\Program Files\jdk1.7.0_71,所以 CLASSPATH 等价于 .;D:\Program Files\jdk1.7.0_71\lib。
Java 运行环境将依次到下面的路径寻找并载入字节码文件 Test.class:
? .p1\Test.class("."表示当前路径)
? D:\Program Files\jdk1.7.0_71\lib\p1\Test.class
如果在第一个路径下找到了所需的类文件,则停止搜索,否则继续搜索后面的路径,如果在所有的路径下都未能找到所需的类文件,则编译或运行出错。
你可以在CLASSPATH变量中增加搜索路径,例如 .;%JAVA_HOME%\lib;C:\javalib,那么你就可以将类文件放在 C:\javalib 目录下,Java运行环境一样会找到。
Java基础语法:
1:在Java中,整型数据的长度与平台无关
自动数据类型转换
自动转换按从低到高的顺序转换。不同类型数据间的优先关系如下:
低--------------------------------------------->高
byte,short,char-> int -> long -> float -> double
字符串拼接:
总结:
1.concat的计算效率要比+的效率高
2.concat只适用于string和string的拼接,+适用于string和任何对象的拼接
3.当在少量的数据拼接时,使用concat和+都行,如果是大量的数据拼接,建议使用StringBuilder或者StringBuffer.
StringBuffer:
String 的值是不可变的,每次对String的操作都会生成新的String对象,不仅效率低,而且耗费大量内存空间。
StringBuffer类和String类一样,也用来表示字符串,但是StringBuffer的内部实现方式和String不同,在进行字符串处理时,不生成新的对象,在内存使用上要优于String。
StringBuffer 默认分配16字节长度的缓冲区,当字符串超过该大小时,会自动增加缓冲区长度,而不是生成新的对象。
StringBuffer不像String,只能通过 new 来创建对象,不支持简写方式
StringBuffer strBuf = new StringBuffer(“www.baidu.com”);
StringBuffer类中的方法主要偏重于对于字符串的操作,例如追加、插入和删除等,这个也是StringBuffer类和String类的主要区别。实际开发中,如果需要对一个字符串进行频繁的修改,建议使用 StringBuffer。
- append() 方法
- deleteCharAt()
- insert() 方法
- setCharAt() 方法
setCharAt() 方法用来修改指定位置的字符
强烈建议在涉及大量字符串操作时使用StringBuffer
StringBuilder:
StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点
CharSequence:
CharSequence类和String类都可以定义字符串,但是String定义的字符串只能读,CharSequence定义的字符串是可读可写的;
对于抽象类或者接口来说不可以直接使用new的方式创建对象,但是可以直接给它赋值;
CharSequence是一个定义字符串操作的接口,它只包括length()、charAt(int index)、subSequence(int start, int end) 这几个API。
CharSequence转换String:str = cs.toString();
String转换CharSequence:cs = str;
CharSequence和String类型的比较:cs.toString().equal(str);
总结
线程安全:
? StringBuffer:线程安全
? StringBuilder:线程不安全
速度:
一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。
使用环境:
? 操作少量的数据使用 String;
? 单线程操作大量数据使用 StringBuilder;
? 多线程操作大量数据使用 StringBuffer。
Java对象及类:
- public class className {
-
// body of class
- }
- private boolean myFlag;
- static final double weeks = 9.5;
- protected static final int BOXWIDTH = 42;
- public static void main(String[] arguments) {
-
// body of method
- }
Java程序的main() 方法必须设置成公有的,否则,Java解释器将不能运行该类
protected:受保护的
被声明为protected的变量、方法和构造方法能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。
protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected
默认的:不使用任何关键字
不使用任何修饰符声明的属性和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为public static final,而接口里的方法默认情况下访问权限为public
访问控制和继承
请注意以下方法继承(不了解继承概念的读者可以跳过这里,或者点击 Java继承和多态 预览)的规则:
? 父类中声明为public的方法在子类中也必须为public。
? 父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
? 父类中默认修饰符声明的方法,能够在子类中声明为private。
? 父类中声明为private的方法,不能够被继承
重载:
? 声明为final的方法不能被重载。
? 声明为static的方法不能被重载,但是能够被再次声明。
方法覆盖的原则:
? 覆盖方法的返回类型、方法名称、参数列表必须与原方法的相同。
? 覆盖方法不能比原方法访问性差(即访问权限不允许缩小)。
? 覆盖方法不能比原方法抛出更多的异常。
? 被覆盖的方法不能是final类型,因为final修饰的方法是无法覆盖的。
? 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并
抽象和接口:
在抽象类中,可以包含一个或多个抽象方法;但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”。
接口:
接口使用 interface 关键字来声明,可以看做是一种特殊的抽象类,可以指定一个类必须做什么,而不是规定它如何去做。
抽象:
使用abstract关键字来声明
区别:
-
抽象类可以为部分方法提供实现,避免了在子类中重复实现这些方法,提高了代码的可重用性,这是抽象类的优势;而接口中只能包含抽象方法,不能包含任何实现。
-
一个类只能继承一个直接的父类(可能是抽象类),但一个类可以实现多个接口,这个就是接口的优势
综上所述,接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
? 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
? 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能
异常:
Java异常处理通过5个关键字控制:try、catch、throw、throws和 finally
任何在方法返回前绝对被执行的代码被放置在finally块中
finally {
// block of code to be executed before try block ends
}
throw与throws的比较
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
好的编程习惯:
1.在写程序时,对可能会出现异常的部分通常要用try{…}catch{…}去捕捉它并对它进行处理;
2.用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理,那怕是最简单的一句输出语句,或栈输出e.printStackTrace();
3.如果是捕捉IO输入输出流中的异常,一定要在try{…}catch{…}后加finally{…}把输入输出流关闭;
4.如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理
finally创建一个代码块。该代码块在一个try/catch 块完成之后另一个try/catch出现之前执行。finally块无论有没有异常抛出都会执行。finally子句是可选项,可以有也可以无。然而每一个try语句至少需要一个catch或finally子句。
注意:如果finally块与一个try联合使用,finally块将在try结束之前执行。
断言:
断言有两种方法:
? 一种是 assert<<布尔表达式>> ;
? 另一种是 assert<<布尔表达式>> :<<细节描述>>。
assert后面跟个冒号表达式。如果冒烟前为true,则冒号后面的被忽略,否则抛出AssertionError,错误内容为冒号后面的内容
Objects类与Object类
Objects类是final修饰的类,不可继承,内部方法都是static方法,从jdk1.7开始才引入了Objects类
如果一个类没有指定父类,默认就是继承Object类。
Object类里面共有11个方法:
经常用到的equals(),toString()都是直接使用或者重写的Object里面的这些方法;
多线程:
Java集合类
集合和数组不一样, 数组元素既可以是基本类型的值, 也可以是对象(实际上保存的是对象的引用变量), 而集合里只能保存对象(实际上只是保存对象的引用变量, 但通常习惯上认为集合里保存的是对象)
Java的集合类主要由两个接口派生而出:Collection, Map, Collection和Map是Java集合框架的根接口, 这两个接口又包含了一些子接口或实现类.
Collection:
Set:无需集合, 元素不可重复
Queue:队列
List:有序集合, 元素可以重复, ArrayList(非线程安全), Vector(线程安全, 古老的集合, 性能较差, 不建议使用.)
Collections工具类, 它可以将一个ArrayList变成线程安全的.
由于Stack继承了Vector, 因此它是一个非常古老的Java集合类, 它同样是线程安全的, 性能较差的, 因此尽量少用Stack类, 如果程序需要使用”栈”这种数据结构, 则可以考虑使用ArrayDeque.
Map:
Key-Value, key不可重复,
HashMap: 线程不安全, key, value允许为null
Hashtable: 线程安全, key, value不允许为null
HashSet和TreeSet是Set的两个典型实现, 到底如何选择HashSet和TreeSet呢? HashSet的性能总是比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合元素的次序.只有当需要一个保持排序的Set时, 才应该使用TreeSet, 否则都应该使用HashSet.
对于一般的场景, 程序应该多考虑使用HashMap, 因为HashMap正是为快速查询设计的(HashMap底层其实也是采用数组来存储的key-value), 但如果程序需要一个总是排好序的Map时, 则可以考虑使用TreeMap.
本地联调环境搭建:
用postMan或者其他rest工具调用iac接口,进行本地联调测试时:
注释掉:
//transServerService.decryptData(dsts);
数据库操作:
DriverManager类
DriverManager类处理驱动程序的加载和建立新数据库连接。DriverManager是java.sql包中用于管理数据库驱动程序的类。通常,应用程序只使用类DriverManager的getConnection()静态方法,用来建立与数据库的连接,返回Connection对象:
static Connection getConnection(String url,String username,String password)
指定数据的URL用户名和密码创建数据库连接对象。url的语法格式是:
jdbc:<数据库的连接机制>:<ODBC数据库名>
Connection类
Connection类是java.sql包中用于处理与特定数据库连接的类。Connection对象是用来表示数据库连接的对象,Java程序对数据库的操作都在这种对象上进行。Connection类的主要方法有:
- Statement createStatement():创建一个Statement对象。
- Statement createStatement(int resultSetType,int resultSetConcurrency):创建一个Statement对象,生成具有特定类型的结果集。
- void commit():提交对数据库的改动并释放当前持有的数据库的锁。
- void rollback():回滚当前事务中的所有改动并释放当前连接持有的数据库的锁。
- String getCatalog():获得连接对象的当前目录。
- boolean isClose():判断连接是否已关闭。
- boolean isReadOnly():判断连接是否为只读模式。
- void setReadOnly():设置连接为只读模式。
- void close():释放连接对象的数据库和JDBC资源。
Statement类
Statement类是java.sql包中用于在指定的连接中处理SQL语句的类。数据库编程的要点是在程序中嵌入SQL命令。程序需要声明和创建连接数据库的Connection对象,并让该对象连接数据库。调用类DriverManager的静态方法getConnection()获得Connection对象,实现程序与数据库的连。然后,用Statement类声明SQL语句对象,并调用Connection对象的createStatement()方法,创建SQL语句对象。例如,以下代码创建语句对象sql:
Statement sql = null;
try{
sql = con.createStatement();
}catch(SQLException e){}
ResultSet类
有了SQL语句对象后,调用语句对象的方法executeQuery()执行SQL查询,并将查询结果存放在一个用ResultSet类声明的对象中,例如,以下代码读取学生成绩表存于rs 对象中:
ResultSet rs = sql.executeQuery(“SELECT * FROM ksInfo”);
ResultSet对象实际上是一个由查询结果数据的表,是一个管式数据集,由统一形式的数据行组成,一行对应一条查询记录。在ResultSet对象中隐含着一个游标,一次只能获得游标当前所指的数据行,用next方法可取下一个数据行。用数据行的字段(列)名称或位置索引(自1开始)调用形如getXXX()方法获得记录的字段植 。以下是ResultSet对象的部分方法: - byte getByte(int columnIndex):返回指定字段的字节值。
- Date getDate(int columnIndex):返回指定字段的日期值。
- float getFloat(int columnIndex):返回指定字段的浮点值。
- int getInt(int columnIndex):返回指定字段的整数值。
- String getString(int columnIndex):返回指定字段的字符串值。
- double getDouble(String columnName):返回指定字段的双精度值。
- long getLong(String columnName):返回指定字段的long型整值。
- boolean next():返回是否还有下一字段。
以上方法中的columnIndex是位置索引,用于指定字段,columnName是字段名。
用户需要在查询结果集上浏览,或前后移动、或显示结果集的指定记录,这称为可滚动结果集。程序要获得一个可滚动结果集,只要在获得SQL的语句对象时,增加指定结果集的两个参数即可。例如,以下代码:
Statement stmt = con.createStatement(type,concurrency);
ResultSet rs = stmt.executeQuery(SQL语句)
语句对象stmt的SQL查询就能得到相应类型的结果集。
? int 型参数type决定可滚动集的滚动方式:
? ResultSet.TYPE_FORWORD_ONLY,结果集的游标只能向下滚动。
? ResultSet.TYPE_SCROLL_INSENSITIVE,游标可上下移动,当数据库变化时,当前结果集不变。
? ResultSet. TYPE_SCROLL_SENSITIVE,游标可上下移动,当数据库变化时,当前结果集同步改变。
? int 型参数concurrency决定数据库是否与可滚动集同步更新:
? ResultSet.CONCUR_READ_ONLY,不能用结果集更新数据库中的表。
? ResultSet.CONCUR_UPDATETABLE,能用结果集更新数据库中的表。
例如,以下代码利用连接对象connect,创建Statement对象stmt,指定结果集可滚动,并以只读方式读数据库:
stmt = connect.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
可滚动集上另外一些常用的方法如下:
- boolean previous():将游标向上移动,当移到结果集的第一行时,返回false。
- void beforeFirst():将游标移结果集的第一行之前。
- void afterLast():将游标移到结果集的最后一行之后。
- void first():将游标移到第一行。
- void last():将游标移到最后一行。
- boolean isAfterLast():判游标是否在最后一行之后。
- boolean isBeforeFirst():判游标是否在第一行之前。
- boolean isLast():判游标是否在最后一行。
- boolean isFirst():判游标是否在第一行。
- int getRow():获取当前所指的行(行号自1开始编号,结果集空,返回0)。
- boolean absolute(int row):将游标移到row行。
破解数据库中的密码:
选择编译dms项目中的APP2, 然后按照截图填入项目参数,
Main函数中的入参为数据库中的密码, 其它参数为数据库security_unit表中, 如下图2
Spring boot框架:
@Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。
Spring注解:
@ComponentScan(basePackages = {“com.hikvision.daf.device.das.controller”,“com.hikvision.daf.device.controller”,“com.hikvision.daf.common.result”})
//自动扫描指定包下所有使用@service, @component, @Controller @Repository的类并注册
https://www.cnblogs.com/cocoxu1992/p/10554889.html
@Repository
用于标注数据访问组件,即DAO组件
@Component
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
//https://www.jianshu.com/p/8d3f5cede6bf
遇到的问题
踩到一个坑,有一个接口,在这个接口的实现类里,需要用到@Autowired注解,一时大意,没有在实现类上加上@Component注解,导致了Spring报错,找不到这个类
一旦使用关于Spring的注解出现在类里,例如我在实现类中用到了@Autowired注解,被注解的这个类是从Spring容器中取出来的,那调用的实现类也需要被Spring容器管理,加上@Component
@Component("conversionImpl")
//其实默认的spring中的Bean id 为 conversionImpl(首字母小写)
public class ConversionImpl implements Conversion {
@Autowired
private RedisClient redisClient;
}
介绍
开发中难免会遇到这个这个注解@Component
@controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层
@service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理
@repository(实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
.
@component (把普通pojo实例化到spring容器中,相当于配置文件中的
)
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
@Resource:
@Resource的作用相当于@Autowired
只不过@Autowired按byType自动注入,
而@Resource默认按 byName自动注入罢了。
@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Resource装配顺序:
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
Bean
@Configuration、@Bean配置详解
(1)@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的< beans>,作用为:配置spring容器(应用上下文)
(2)在@Configuration加上@Bean去注册一个bean 对象,这样我们就不用再去写xml文件去注册bean对象。@Bean标注在方法上(返回某个实例的方法)。
WAR包解压成JAR包及JAR包打包成WAR包:
1、打包 C:\Temp\jar -cvf Blog.war ./*
2、解压 jar -xvf Blog.war
数据库事务处理:
1:@Transactional:这个注解可以标注在类或者方法上, 当它标注在类上时, 代表这个类所有公共非静态的方法都将启用事务功能.建议放在实现类上.
2:@PostConstruct:Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行;
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
3:数据库隔离级别, 用法:
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1, propagation = Propagation.REQUIRED)
Spring提供了四类隔离级别, 分别为未提交读, 读写提交, 可重复读, 串行化.
1.未提交读是最低的隔离级别, 其含义是允许一个事务读取另外一个事务没有提交的数据, 未提交读一中危险的隔离级别.,优点是高并发能力强.
2.读写提交, 是指一个事务只能读取另外一个事务已经提交的数据.不能读取未提交的数据.
3.可重复读,目标是克服读提交中出现不可重复读的现象,待事务提交后才可读取.
4:串行化是数据库最高的隔离级别, 它会要求所有的SQL都会按照顺序执行,这样就可以克服上述隔离级别出现的问题,所以它能够保证数据的一致性.
总结: 在现实中一般而言, 选择隔离级别会以读写提交为主, 它能够防止脏读, 而不能避免重复读,为了克服数据不一致和性能问题, 程序开发者还设计了乐观锁, 甚至不再使用数据库而使用其它手段, 比如Redis.
5:@Transactional自调用失效问题,数据库事务的约定, 其实现原理是AOP, 而AOP的原理是动态代理, 在自调用的过程中, 是类自身的调用,而不是代理对象去调用, 那么就不会产生AOP, 这样Spring就不会把你的代码织入预定的流程中, 于是就失败了.
Redis
1:@EnableCaching:使用Spring缓存注解操作Redis, 为了使用缓存管理器, 需要在Spring boot的配置文件中加入驱动缓存的注解EnableCaching, 这样就可以驱动Spring缓存机制工作了.
抽象类和接口的区别
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
下面看一个网上流传最广泛的例子:门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:
abstract class Door {
public abstract void open();
public abstract void close();
}
或者:
interface Door {
public abstract void open();
public abstract void close();
}
但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:
1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;
2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。
从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() {
//…
}
void close() {
//…
}
void alarm() {
//…
}
}
内容总结
以上是互联网集市为您收集整理的JAVA学习笔记全部内容,希望文章能够帮你解决JAVA学习笔记所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。