第4节:Java基础 - 必知必会(中)

第4节:Java基础 - 必知必会(中)

本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点。

一、抽象类和接口有什么区别

抽象类和接口的主要区别可以总结如下:

  • 抽象类中可以没有抽象方法,JDK8版本开始提供了接口总方法的default实现

  • 抽象类和类一样是单继承的;接口可以实现多个父类

  • 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只能有常量,没有变量

    解析

    在Java中,我们用abstract来定义抽象类,通过interface关键字来定义接口。接口和抽象类中都可以定义抽象方法,然后交由其实现类来实现该抽象方法。

    抽象类和接口应该如何选择?分别在什么情况下使用呢?

根据抽象类和接口的不同之处,当我们仅仅需要定义一些抽象方法而不需要其余二外的具体方法或者变量的时候,我们嗯可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量

默认方法

  既然说到了JDK8接口中的方法可以实现,那么我们来看下default方法的具体实现。我们先给出一个接口中的default方法Demo,如下所示:

public interface MyInterface {
    // 定义一个已经实现的方法,使用default表明
    default void say(String message){
        System.out.println("Hello "+message);
    }
    // 普通的抽象方法
    void test();
}

  当一个类实现该接口时,可以继承到该接口中的默认方法,如下所示:

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如下:

public 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...");
    }
}

这个时候,实现类那里会编译错误 .我们有两种处理方式,如下所示:

  • 重写多个接口中的相同的默认方法

  • 在实现类中指定要使用哪个接口中的默认方法

a、重写多个接口中的相同的默认方法

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...");
    }
}

b、在实现类中指定要使用哪个接口中的默认方法:

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。这个题目的答案,我希望大家可以自己动手输入代码来一一查看以加深记忆。这里给出每种基本类型所占用的字节数:

  • 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注解就只能作用 在类/接口和方法上。

 @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnn { } 

@Rentention

保留策略定义了该注解被保留的时间长短。关键源码如下:

 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就是属性,看着是一个方法,但我们称它为属性。当为注解指定属性后,那么在使用注解时就必须要给属性赋值了。

四、Java中反射机制

反射机制是指在运行中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。即动态获取信息和动态调用对象方法的功能称为反射机制。

反射机制的作用

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时调用任意一个对象的方法,生成动态代理

    与反射相关的类:

  • Class:表示类,用于获取类的相关信息

  • Field:表示成员变量,用于获取实例变量和静态变量等

  • Method:表示方法,用于获取类中的方法参数和方法类型等

  • Constructor:表示构造器,用于获取构造器的相关参数和类型等

这里我们讲述下如何获取Class类吧,获取Class类有三种基本方式:

(1)通过类名称.class来获取Class类对象:

Class c = int.class;
Class c = int[ ].class;
Class c = String.class

  

(2)通过对象.getClass( )方法来获取Class类对象:

Class c = obj.getClass( );

(3)通过类名称加载类Class.forName( ),只要有类名称就可以得到Class:

Class c = Class.forName(“cn.ywq.Demo”);

接下来,我们给出一个以反射方式来创建对象的Demo:

package com.ywq;
 
public class Demo1 {
    public static void main(String[] args) throws Exception {
        String className = "com.ywq.User";
        // 获取Class对象
        Class clazz = Class.forName(className);
        // 创建User对象
        User user = (User)clazz.newInstance();
        // 和普通对象一样,可以设置属性值
        user.setUsername("yangwenqiang");
        user.setPassword("19931020");
 
        System.out.println(user);
    }
}
 
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 + "]";
    }
}

附加

clazz.newInstance()和clazz.new()区别总结如下:

1.构造方法实例化, 会判断实例化的对象的Class是否已经加载,如果没有加载需先加载类,然后初始化类,返回一个实例.

clazz.newInstance()方法必须在类加载完成之后才能调用.也就是说,这一步不会有类加载的步骤.类加载必须在此之前加载完成,否则不能调用该方法.

比如Class.forName(Stirng clazzName)包含加载和连接两个阶段,连接阶段包含:验证,准备,解析三个阶段;

然后初始化类,返回一个实例

2.clazz调用的是无参构造方法,而实例化构造方法使用有参构造方法. 当然需要public修饰. 反正要能访问的到.

3.clazz反射可以用来解耦合,动态的构造实例. 比如IOC控制反转,以及基于接口编程.可以把实现类或者子类,强转为接口类或父。

关于反射这块的详细API接口介绍,建议大家参考官方提供的API接口文档 。

总结

本小节是Java基础篇章的第二小节,本小节中介绍的接口和抽象类的区别,建议大家熟练掌握。注解和反射机制在Java基础中具有一定的难度,希望大家可以多加理解与掌握。

 

 

猜你喜欢

转载自www.cnblogs.com/csushl/p/11964872.html