阿里架构师讲面试:java基础语法拾遗

298 阅读15分钟

这些基础语法知识,不仅是因为面试出现频次极高,而且在日常工作中也会经常遇到。因此,理解清楚这些基础语法语义和用法,对日常coding会有很大的帮助。千万不要轻视大路边上的基础知识,否则很有可能哪天阴沟里翻船。

Static,Final关键字

static指定为类变量,生命周期跟着类。

final指该变量不可变,注意如果修饰的是对象,则指变量引用的地址不能变,但是引用的对象内容是可以改变的。

反射

Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。

原理

获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方法。(根据类的全限定名去类对象里面找就可以了,如果没有,则从文件中加载)

反射的实现方式

获取Class对象,有4中方法:

1)Class.forName(“类的路径”);

2)类名.class

3)对象名.getClass()

4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象

为什么要使用

java的反射机制就是增加程序的灵活性,避免将程序写死到代码里, 例如: 实例化一个 person()对象, 不使用反射,用new person(), 如果想变成实例化其他类,那么必须修改源代码,并重新编译。 使用反射: class.forName("Person").newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。

比如汇率生成阶段给策略选择算法,算法模板可以配置在数据库中,应用在运行阶段从数据库中读取算法名称然后通过反射进行实例化调用。这种做法兼顾了灵活性和扩展性。当希望新增一个算法时,只需要实现算法接口即可,很容易扩展。

  • 扩展性

反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。

  • 灵活性

在编码阶段不知道那个类名,要在运行期从配置文件读取类名,这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象。

缺点

  • 性能问题

使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此J**ava反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,**普通程序不建议使用。

  • 使用反射会模糊程序内部逻辑

程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。<比如接口多态场景,看代码并不能知道调用哪个handler,还需要结合DB配置看>

常见使用场景

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。

**反射最重要的用途就是开发各种通用框架。**很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

再比如加载jdbc驱动:

public class ConnectionJDBC {  

    /** 
     * @param args 
     */  
    //驱动程序就是之前在classpath中配置的JDBC的驱动程序的JAR 包中  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    //连接地址是由各个数据库生产商单独提供的,所以需要单独记住  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    //连接数据库的用户名  
    public static final String DBUSER = "root";  
    //连接数据库的密码  
    public static final String DBPASS = "";  

    public static void main(String[] args) throws Exception {  
        Connection con = null; //表示数据库的连接对象  
        Class.forName(DBDRIVER); //1、使用CLASS 类加载驱动程序 ,反射机制的体现 
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连接数据库  
        System.out.println(con);  
        con.close(); // 3、关闭数据库  
    }  

泛型

  • 泛型类

泛型类定义时只需要在类名后面加上类型参数即可,当然你也可以添加多个参数,类似于<K,V>,<T,E,K>等。这样我们就可以在类里面使用定义的类型参数。

泛型类最常用的使用场景就是“元组”的使用。我们知道方法return返回值只能返回单个对象。如果我们定义一个泛型类,定义2个甚至3个类型参数,这样我们return对象的时候,构建这样一个“元组”数据,通过泛型传入多个对象,这样我们就可以一次性方法多个数据了。

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());
  • 泛型接口
//定义一个泛型接口
public interface Generator<T> {
    public T next();
}/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}
/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}
  • 泛型方法

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> t or T t)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}
Object obj = genericMethod(Class.forName("com.test.test"));

上述,T t,为对象实例传入,Class t为类对象传入用法。

blog.csdn.net/s10461/arti…

接口和抽象类

  • 什么是抽象类?

就是对类更高的抽象。抽象类作为多个子类的共同父类。它所体现的是一种模版设计,抽象类作为多个子类的父类,可以把它理解为系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但是不能当成最终产品,还需要进一步的完善。

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法【抽象方法】,用abstract来修饰该类【抽象类】。

  • 抽象类必须有抽象方法吗?

用abstract修饰的类就是抽象类,并不是说抽象类中必须有抽象方法,即使一个类中的方法全部实现过,也可以用abstract修饰为抽象类,所以抽象类不一定都有抽象方法。

  • 抽象类可以实例化吗?

在抽象类中可以有构造方法,只是不能直接创建抽象类的实例对象。但实例化子类的时候,就会初始化父类,不管父类是不是抽象类都会调用父类的构造方法,初始化一个类,先初始化父类。

因为抽象类中含有无具体实现的方法(定义是这样,但是声明为抽象类中没有抽象方法的特殊情况,也按照定义约定),所以不能用抽象类创建对象。

  • 接口是什么?

接口是一种规范,就像现实中生产主板和内存条或者网卡的不是同一家产商,但是为何内存或者网卡插入到主板上就能用呢,因为他们都遵守了某种规范,然后就可以使用。虽然他们的内部实现可能完全不同。就好比在java语言中的方法内部实现你不需要关心,只需要知道这个接口是怎样的干嘛的就行了,直接用。

  • 接口中的变量和方法?

接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。

www.cnblogs.com/dolphin0520…

内部类

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。内部类的设计主要是为了打破类单继承约束。

  • 成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,可以看成外部类的一个成员变量。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

class Circle {
    double radius = 0;

    public Circle(double radius) {
        this.radius = radius;
    }

    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println("drawshape");
        }
    }
}

不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

外部类.this.成员变量
外部类.this.成员方法

成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。也可以理解,毕竟内部权限更高。

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。

  • 静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似。

它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

  • 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

  • 匿名内部类

参考:www.cnblogs.com/dolphin0520…

重写(Override)和重载(Overload)有什么区别?

  • java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。
  • 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。

但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。

方法重写又称方法覆盖。

equals 和 == 的区别

  • 对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

  • 基本类型:比较的是值是否相同;

  • 引用类型:比较的是引用是否相同,也就是是变量引用的值在内存中的存放地址是否相同;

  • JAVA当中所有的类都是继承于Object这个超类的,在Object类中定义了一个equals的方法,equals的源码是这样写的:

public boolean equals(Object obj) {
    //this - s1
    //obj - s2
    return (this == obj);
}

可以看到,这个方法的初始默认行为是比较对象的内存地址值,一般来说,意义不大。所以,在一些类库当中这个方法被重写了,如String、Integer、Date。在这些类当中equals有其自身的实现(一般都是用来比较对象的成员变量值是否相同),而不再是比较类在堆内存中的存放地址了。

深拷贝和浅拷贝

  • 浅拷贝

  • 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址的值,就会影响到另一个对象。

  • 深拷贝

  • 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。

如何实现深拷贝?

  • 自定义类要实现Cloneable接口,并覆写clone()方法。
//测试类1
class Person implements Cloneable{
        String name;
        int age;
        Person(String name,int age){
                this.name=name;
                this.age=age;
        }
        @Override
        public Object clone() {
                try{
                        return  super.clone();
                }catch(CloneNotSupportedException e){
                       return null;
                }
        }
}
//测试类2

class Animal implements Cloneable{
        Person host;//主人
        int age;//年纪
        Animal(Person person,int age){
                this.host=person;
                this.age=age;
        }
        @Override
        public Object clone(){
                try{
                        Animal animal=(Animal) super.clone();
                        animal.host=(Person)host.clone();//深拷贝处理
                        return animal;
                }catch (CloneNotSupportedException e){
                        return null;
                }
        }
}
  • 通过序列化方式实现深拷贝:先将要拷贝对象写入到内存中的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和原对象就不存在任何地址上的共享,自然实现了深拷贝。

自定义类需要实现Serializable接口。

//工具类
class CloneUtil{
        public static <T extends Serializable> T clone(T obj){
                T cloneObj=null;
                try{
                        //写入字节流
                        ByteArrayOutputStream baos=new ByteArrayOutputStream();
                        ObjectOutputStream oos=new ObjectOutputStream(baos);
                        oos.writeObject(obj);
                        oos.close();

                        //分配内存,写入原始对象,生成新对象
                        ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());//获取上面的输出字节流
                        ObjectInputStream ois=new ObjectInputStream(bais);

                        //返回生成的新对象
                        cloneObj=(T)ois.readObject();
                        ois.close();
                }catch (Exception e){
                       e.printStackTrace();
                }
                return cloneObj;
        }
}

//测试类1
class Person implements Serializable{
        String name;
        int age;
        Person(String name,int age){
                this.name=name;
                this.age=age;
        }

}
//测试类2

class Animal implements Serializable{
        Person host;//主人
        int age;//年纪
        Animal(Person person,int age){
                this.host=person;
                this.age=age;
        }
}

juejin.cn/post/684490…

觉得有收获的话帮忙点个赞吧,让有用的知识分享给更多的人

## 欢迎关注掘金号:五点半社

## 欢迎关注微信公众号:五点半社(工薪族的财商启蒙)##