文章目录
抽象类和接口有什么区别
- 抽象类中可以没有抽象方法,也可以抽象方法和非抽象方法共存
- 接口中的方法在JDK8之前只能是抽象的,JDK8版本开始提供了接口中方法的default实现
- 抽象类和类一样是单继承的;接口可以实现多个父接口
- 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量
抽象类和接口应该如何选择?分别在什么情况下使用呢?
根据抽象类和接口的不同之处,当我们仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量。
默认方法(default)
JDK8接口中的方法可以实现,那么我们来看下default方法的具体实现。我们先给出一个接口中的default方法Demo,如下所示:
public interface MyInterface {
// 定义一个已经实现的方法,使用default表明
default void say(String message){
System.out.println("Hello "+message);
}
// 普通的抽象方法
void test();
}
class MyClass implements MyInterface{
@Override
public void test() {
System.out.println("test...");
}
}
class Main{
public static void main(String[] args) {
MyClass client = new MyClass();
client.test();
client.say("World...");
}
}
如果两个接口中存在同样的默认方法,实现类继承的是哪一个呢?
Demo如下:
ublic interface MyInterface {
// 定义一个已经实现的方法,使用default表明
default void say(String message){
System.out.println("Hello "+message);
}
// 普通的抽象方法
void test();
}
interface MyInterface2{
// 定义一个已经实现的方法,使用default表明
default void say(String message){
System.out.println("[2]-Hello "+message);
}
}
// 此处会编译错误
class MyClass implements MyInterface, MyInterface2{
@Override
public void test() {
System.out.println("test...");
}
}
这个时候,实现类那里会编译错误。因为有两个相同的方法,编译器不知道该如何选择了。
我们有两种处理方式
- 重写多个接口中的相同的默认方法
class MyClass implements MyInterface, MyInterface2{
@Override
public void say(String message) {
System.out.println("[Client]-Hello "+message);
}
@Override
public void test() {
System.out.println("test...");
}
}
- 在实现类中指定要使用哪个接口中的默认方法
class MyClass implements MyInterface, MyInterface2{
// 手动指定哪个默认方法生效
public void say(String message) {
MyInterface.super.say(message);
}
@Override
public void test() {
System.out.println("test...");
}
}
JDK8中为什么会出现默认方法呢?
使用接口,使得我们可以面向抽象编程,但是其有一个缺点就是当接口中有改动的时候,需要修改所有的实现类。在JDK8中,为了给已经存在的接口增加新的方法并且不影响已有的实现,所以引入了接口中的默认方法实现。(因为一加方法,其余实现该接口的类都需要去实现了)
默认方法允许在不打破现有继承体系的基础上改进接口,解决了接口的修改与现有的实现不兼容的问题。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。在我们实际开发中,接口的默认方法应该谨慎使用,因为在复杂的继承体系中,默认方法可能引起歧义和编译错误。
Java中的8种基本数据类型及其取值范围
java种的8种基本数据类型分别是:byte,short,int,long,float,double,char以及boolean。boolean类型的取值为true和false两种,其余每一种基本类型都占有一定的字节,并且拥有着最大值和最小值。比如int的取值范围为 Integer.MIN_VALUE 到 Integer.MAX_VALUE。
演示
public class Test {
public static void main(String[] args) {
int a=Integer.MAX_VALUE+1;
int a2=Integer.MAX_VALUE;
System.out.println(a);
System.out.println(a2);
System.out.println("----------------------");
int a3=Integer.MIN_VALUE-1;
int a4=Integer.MIN_VALUE;
System.out.println(a3);
System.out.println(a4);
}
}
每种基本类型所占用的字节数:
- byte:1字节
- short:2字节
- int:4个字节
- long:8字节
- float:4字节
- double:8字节
- char:2字节
- boolean:Java规范中并没有规定boolean类型所占字节数
各个基本类型所占的字节数,需要准确记忆与理解。关于取值范围,如果实在记忆有点缺失,记住通过哪个字段可以获取到其取值范围也可。
java元注解
Java中提供了4个元注解,元注解的作用是负责注解其它注解。
- @Target:
说明注解所修饰的对象范围,关键源码如下:
public @interface Target {
ElementType[] value();
}
public enum ElementType {
TYPE,FIELD,METHOD,PARAMETED,CONSTRUCTOR,LOCAL_VARIABLE,ANNOCATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE
}
例如,如下的注解使用@Target标注,表明MyAnn注解就只能作用在类/接口和方法上。
//TYPE指类,METHOD指方法
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnn {
}
- @Retention:(保留策略)
保留策略定义了该注解被保留的时间长短。关键源码如下:
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
SOURCE, CLASS, RUNTIME
}
其中,SOURCE:表示在源文件中有效(即源文件保留);CLASS:表示在class文件中有效(即class保留);RUNTIME:表示在运行时有效(即运行时保留)。
例如,@Retention(RetentionPolicy.RUNTIME)标注表示该注解在运行时有效。
- @Documented
该注解用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被javadoc此类的工具文档化。Documented是一个标记注解,没有成员。关键源码如下:
public @interface Documented {
}
- @Inherited
该注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。关键源码如下:
public @interface Inherited {
}
总结:
Java中的注解是一个基础知识点,我们在程序中频繁的使用注解。使用注解可以代替配置文件,比如SpringBoot就是提供了大量的注解来代替配置文件,从而极大的方便了Web项目的搭建与开发。
详细阐述下Java中的注解
- 注解的作用:代替繁杂的配置文件,简化开发。
- 如何定义一个注解?
定义注解类不能使用class、enum以及interface,必须使用@interface。下边是一个简单的注解定义:
public @interface MyAnn{}
- 如何定义注解的属性?
public @interface MyAnn {
String value();
int value1();
}
// 使用注解MyAnn,可以设置属性
@MyAnn(value1=100,value="hello")
public class MyClass {
}
定义注解时候的value就是属性,看着是一个方法,但我们称它为属性。当为注解指定属性后,那么在使用注解时就必须要给属性赋值了。
总结:
我们上边仅仅是对注解进行了一个定义,当定义一个注解之后,还需要一个注解处理器来执行注解的内部逻辑。注解处理器定义了注解的处理逻辑,涉及到反射机制和线程机制等。
注解处理器
待更
反射机制
反射机制是指在运行中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。即动态获取信息和动态调用对象方法的功能称为反射机制。
反射机制的作用
- 在运行时判断任意一个对象所属的类
- 在运行时构造一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法,生成动态代理
与反射相关的类
- Class:表示类,用于获取类的相关信息
- Field:表示成员变量,用于获取实例变量和静态变量等
- Method:表示方法,用于获取类中的方法参数和方法类型等
- Constructor:表示构造器,用于获取构造器的相关参数和类型等
获取Class类有三种基本方式
- 通过类名称.class来获取Class类对象
Class c = int.class;
Class c = int[ ].class;
Class c = String.class
- 通过对象.getClass( )方法来获取Class类对象
Class c = obj.getClass( );
- 通过类名称加载类Class.forName( ),只要有类名称就可以得到Class
Class c = Class.forName(“cn.ywq.Demo”);
反射Demo
public class Demo {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
String className="com.tyk.reflect.User";
Class clazz=Class.forName(className);
User user=(User)clazz.newInstance();
user.setUsername("tyk");
user.setPassword("100000");
System.out.println(user);
}
}
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [username=" + username + ", password=" + password + "]";
}
}