面经系列之 java基础

java基础

1 值传递和引用传递

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。
一般认为,java内的基础类型数据传递都是值传递. java中实例对象的传递是引用传递。

首先是 基本类型与对象的比较:
在这里插入图片描述
四个例子:

1)基本类型

public static void main(String[]args){
        int n = 10;
        foo(n);
        System.out.println(n);
    }
    public static void foo(int n){
        n = 100000;
    }

输出:10;

2)没有提供改变自身方法的引用类型

public static void main(String[]args){
        String s = "woaibeijing";
        foo(s);
        System.out.println(s);
    }
    public static void foo(String s){
        s = "tianna";
    }

输出:woaibeijing

3)提供了改变自身方法的引用类型

public static void main(String[]args){
        StringBuilder sb = new StringBuilder("iphone");
        foo(sb);
        System.out.println(sb);
    }
    public static void foo(StringBuilder sb){
        sb.append("333");
    }

输出:iphone333
原因:在这里插入图片描述
在这里插入图片描述
4)提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。

public static void main(String[]args){
        StringBuilder sb = new StringBuilder("iphone");
        foo(sb);
        System.out.println(sb);
    }
    public static void foo(StringBuilder sb){
        sb = new StringBuilder("ipad");
    }

输出:iphone
原因:
在这里插入图片描述
在这里插入图片描述

2 为什么重写equals()时也要重写hashcode()

那为什么在重写equals方法时都要重写hashCode方法呢:
首先equals与hashcode间的关系是这样的:

1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)

自我的理解:

由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用;

我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的,那么怎么能保证不能被放入重复的元素呢,但靠equals方法一样比较的话,如果原来集合中以后又10000个元素了,那么放入10001个元素,难道要将前面的所有元素都进行比较,看看是否有重复,欧码噶的,这个效率可想而知,因此hashcode就应遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,那么就用equals比较,如果没有则直接插入,只要就大大减少了equals的使用次数,执行效率就大大提高了。

继续上面的话题,为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode却是不相同的。如果有一个包含某个人员对象的map,如果不重写hashcode,那么会出现map中会出现重名的(因为我们想要以名字区分,但是map内部先采用hashcode区分,可以这么理解?。看以下例子就明白了)。

举例:
以下原因是重点!!!

package Main1230;

import java.util.HashSet;
import java.util.Set;

/**
 * @author zzy
 * @create 2019-12-30 13:40
 */
public class Student {
    int age;
    String name;
    public Student(int age,String name){
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public static void main(String[]args){
        Student s1 = new Student(10,"zhang");
        Student s2 = new Student(10,"zhang");
        Set<Student>set = new HashSet<>();
        set.add(s1);
        set.add(s2);
        System.out.println(set.size());
    }
    @Override
    public boolean equals(Object obj){
        if(obj == null){
            return false;
        }
        if(obj.getClass() != getClass()){
            return false;
        }
        return this.getName() .equals( ((Student)obj).getName());
    }
    //重写equals方法后,size依然为2;

    @Override

    public int hashCode() {
        return getName().hashCode();
    }
    //继续重写hashCode方法,size 为1;
/*
* 原因:
* put方法调用时首先会调用存入元素的hashCode()方法,计算出元
* 素所对应在表中的位置,然后判断这个位置上是否已经有内容了。
* 
* 如果这个位置上以及有了一个元素,那么就调用传入
* 元素的equals()方法与已有的元素进行对比,
* 以此来判断两个元素是否相同,如果不相同,就将这个元素也存入表中。
* 
* 所以假如两个元素是“相同的”,但是在第一步的散列位置就不同,后面就不会判断是否相同了。
* */
}


3 java面向对象特性

1)抽象:将某一类的实体所共有的属性和方法进行抽象;

2)封装:将对象的属性和方法封装到同一个类中,外部对象对类的属性的访问只能通过类的方法进行访问;

3)继承:子对象继承父对象的所有属性和方法;

4)多态:子对象重写父对象中的方法,具体包括:方法的重写和重载;

4 java和c++的区别

Java和C++的区别:

  1. Java是解释型语言,所谓的解释型语言,就是源码会先经过一次编译,成为中间码,中间码再被解释器解释成机器码。对于Java而言,中间码就是字节码(.class),而解释器在JVM中内置了。
  2. C++是编译型语言,所谓编译型语言,就是源码一次编译,直接在编译的过程中链接了,形成了机器码。
  3. C++比Java执行速度快,但是Java可以利用JVM跨平台。
  4. Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程的东西,比如是全局变量和全局函数。
  5. C++中有指针,Java中没有,但是有引用。
  6. C++支持多继承,Java中类都是单继承的。但是继承都有传递性,同时Java中的接口是多继承,类对接口的实现也是多实现。
  7. C++中,开发需要自己去管理内存,但是Java中JVM有自己的GC机制,虽然有自己的GC机制,但是也会出现OOM和内存泄漏的问题。C++中有析构函数,Java中Object的finalize方法。
  8. C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。
  9. 还有很多呢吧…

5 多态的实现原理

有 2 种方式来实现多态, 一种是编译时多态, 另外一种是运行时多态; 编译时多态是通过方法的重载来实现的, 运行时多态是通过方法的重写来实现的。

方法的重载, 指的是同一个类中有多个同名的方法, 但这些方法有着不同的参数。 在编译时就可以确定到底调用哪个方法。

方法的重写, 子类重写父类中的方法。 父类的引用变量不仅可以指向父类的实例对象, 还可以指向子类的实例对象。 当父类的引用指向子类的对象时,只有在运行时才能确定调用哪个方法。体现出多态、动态链接,向上转型

向上转型:父类对象引用子类对象
动态链接:当父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。

1 类调用

1)静态绑定
//被调用的类  
package hr.test;  
class Father{  
      public static void f1(){  
              System.out.println("Father— f1()");  
      }  
}  
//调用静态方法  
import hr.test.Father;  
public class StaticCall{  
       public static void main(){  
            Father.f1(); //调用静态方法  
       }  
}  

上面的源代码中执行方法调用的语句(Father.f1())被编译器编译成了一条指令:invokestatic #13。我们看看JVM是如何处理这条指令的

 (1) 指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。这个常量表(CONSTATN_Methodref_info ) 记录的是**方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)**。JVM会首先根据这个符号引用找到方法f1所在的类的全限定名: hr.test.Father。

 (2) 紧接着JVM会加载、链接和初始化Father类。

 (3) 然后在Father类所在的方法区中找到f1()方法的直接地址,并将这个直接地址记录到StaticCall类的常量池索引为13的常量表中。这个过程叫常量池解析 ,以后再次调用Father.f1()时,将直接找到f1方法的字节码。

 (4) 完成了StaticCall类常量池索引项13的常量表的解析之后,JVM就可以调用f1()方法,并开始解释执行f1()方法中的指令了。



 通过上面的过程,我们发现经过常量池解析之后,JVM就能够确定要调用的f1()方法具体在内存的什么位置上了。实际上,这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式,我们叫做 静态绑定机制 。

 除了被static 修饰的静态方法,所有被private 修饰的私有方法、被final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。另外所有类的初始化方法<init>和<clinit>会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。
2)动态绑定
package hr.test;  
//被调用的父类  
class Father{  
    public void f1(){  
        System.out.println("father-f1()");  
    }  
        public void f1(int i){  
                System.out.println("father-f1()  para-int "+i);  
        }  
}  
//被调用的子类  
class Son extends Father{  
    public void f1(){ //覆盖父类的方法  
        System.out.println("Son-f1()");  
    }  
        public void f1(char c){  
                System.out.println("Son-s1() para-char "+c);  
        }  
}  
  
//调用方法  
import hr.test.*;  
public class AutoCall{  
    public static void main(String[] args){  
        Father father=new Son(); //多态  
        father.f1(); //打印结果: Son-f1()  
    }  
}  

上面的源代码中有三个重要的概念:多态(polymorphism) 、方法覆盖 、方法重载 。打印的结果大家也都比较清楚,但是JVM是如何知道f.f1()调用的是子类Sun中方法而不是Father中的方法呢?在解释这个问题之前,我们首先简单的讲下JVM管理的一个非常重要的数据结构——方法表 。

在JVM加载类的同时,会在方法区中为这个类存放很多信息(详见《Java 虚拟机体系结构 》)。其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。下图是上面源代码中Father和Sun类在方法区中的方法表:
在这里插入图片描述
上图中的方法表有两个特点:
(1) 子类方法表中继承了父类的方法,比如Father extends Object。
(2) 相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。比如Father方法表中的f1()和Son方法表中的f1()都位于各自方法表的第11项中。

对于上面的源代码,编译器首先会把main方法编译成下面的字节码指令:

0  new hr.test.Son [13] //在堆中开辟一个Son对象的内存空间,并将对象引用压入操作数栈  
3  dup    
4  invokespecial #7 [15] // 调用初始化方法来初始化堆中的Son对象   
7  astore_1 //弹出操作数栈的Son对象引用压入局部变量1中  
8  aload_1 //取出局部变量1中的对象引用压入操作数栈  
9  invokevirtual #15 //调用f1()方法  
12  return  

其中invokevirtual指令的详细调用过程是这样的:

(1) invokevirtual指令中的#15指的是AutoCall类的常量池中第15个常量表的索引项。这个常量表(CONSTATN_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到调用方法f1的类的全限定名: hr.test.Father。这是因为调用方法f1的类的对象father声明为Father类型。

(2) 在Father类型的方法表中查找方法f1,如果找到,则将方法f1在方法表中的索引项11(如上图)记录到AutoCall类的常量池中第15个常量表中(常量池解析 )。这里有一点要注意:如果Father类型方法表中没有方法f1,那么即使Son类型中方法表有,编译的时候也通过不了。因为调用方法f1的类的对象father的声明为Father类型。

(3) 在调用invokevirtual指令有一个aload_1指令,它会将开始创建在堆中的Son对象的引用压入操作数栈。然后invokevirtual指令会根据这个Son对象的引用首先找到堆中的Son对象,然后进一步找到Son对象所属类型的方法表。过程如下图所示:

在这里插入图片描述

(4) 这时候通过第(2)步中解析完成的#15常量表中的方法表的索引项11,可以定位到Son类型方法表中的方法f1(),然后通过直接地址找到该方法字节码所在的内存空间。

很明显,根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做 动态绑定机制 。

2 接口调用

因为 Java 类是可以同时实现多个接口的,而当用接口引用调用某个方法的时候,情况就有所不同了。

Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。

interface IDance {
    void dance();
}
 
class Person {
    public String toString() {
        return "I'm a person.";
    }
 
    public void eat() {
    }
 
    public void speak() {
    }
 
}
 
class Dancer extends Person implements IDance {
    public String toString() {
        return "I'm a dancer.";
    }
 
    public void dance() {
    }
}
 
class Snake implements IDance {
    public String toString() {
        return "A snake.";
    }
 
    public void dance() {
        //snake dance  
    }
}

可以看到,由于接口的介入,继承自于接口 IDance 的方法 dance()在类 Dancer 和 Snake 的方法表中的位置已经不一样了,显然我们无法仅根据偏移量来进行方法的调用。

Java 对于接口方法的调用是采用搜索方法表的方式,如,要在Dancer的方法表中找到dance()方法,必须搜索Dancer的整个方法表。

因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的

以上参考:https://www.cnblogs.com/kaleidoscope/p/9790766.html.
https://blog.csdn.net/zero_295813128/article/details/52117737

6 抽象类和接口的区别以及使用场景

1)相同点
1 两者都是抽象类,都不能实例化。
2 interface实现类及abstrct class的子类都必须要实现已经声明的抽象方法。

2)不同点

1.abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。

2.在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。

3.abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。

4.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。

5.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。

6.抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

7.接口中的方法默认都是 public,abstract 类型的。

结论

abstract class 和 interface 是 Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。

7 忘了之前要写啥了

未完待续…

8 反射

1)反射概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

2)获取class文件对象的三种方式:

1 Object类的getClass()方法;
在这里插入图片描述
2 静态属性Class
在这里插入图片描述
3 Class类中静态方法forName()
在这里插入图片描述

3)反射机制的相关类

在这里插入图片描述
相关方法:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

4)示例

public class Book{
    private final static String TAG = "BookTag";

    private String name;
    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }

    public Book() {
    }

    private Book(String name, String author) {
        this.name = name;
        this.author = author;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    private String declaredMethod(int index) {
        String string = null;
        switch (index) {
            case 0:
                string = "I am declaredMethod 1 !";
                break;
            case 1:
                string = "I am declaredMethod 2 !";
                break;
            default:
                string = "I am declaredMethod 1 !";
        }

        return string;
    }
}
public class ReflectClass {
    private final static String TAG = "peter.log.ReflectClass";

    // 创建对象
    public static void reflectNewInstance() {
        try {
            Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
            Object objectBook = classBook.newInstance();
            Book book = (Book) objectBook;
            book.setName("Android进阶之光");
            book.setAuthor("刘望舒");
            Log.d(TAG,"reflectNewInstance book = " + book.toString());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 反射私有的构造方法
    public static void reflectPrivateConstructor() {
        try {
            Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
            Constructor<?> declaredConstructorBook = classBook.getDeclaredConstructor(String.class,String.class);
            declaredConstructorBook.setAccessible(true);
            Object objectBook = declaredConstructorBook.newInstance("Android开发艺术探索","任玉刚");
            Book book = (Book) objectBook;
            Log.d(TAG,"reflectPrivateConstructor book = " + book.toString());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 反射私有属性
    public static void reflectPrivateField() {
        try {
            Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
            Object objectBook = classBook.newInstance();
            Field fieldTag = classBook.getDeclaredField("TAG");
            fieldTag.setAccessible(true);
            String tag = (String) fieldTag.get(objectBook);
            Log.d(TAG,"reflectPrivateField tag = " + tag);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 反射私有方法
    public static void reflectPrivateMethod() {
        try {
            Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
            Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class);
            methodBook.setAccessible(true);
            Object objectBook = classBook.newInstance();
            String string = (String) methodBook.invoke(objectBook,0);

            Log.d(TAG,"reflectPrivateMethod string = " + string);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 获得系统Zenmode值
    public static int getZenMode() {
        int zenMode = -1;
        try {
            Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
            Method mGetService = cServiceManager.getMethod("getService", String.class);
            Object oNotificationManagerService = mGetService.invoke(null, Context.NOTIFICATION_SERVICE);
            Class<?> cINotificationManagerStub = Class.forName("android.app.INotificationManager$Stub");
            Method mAsInterface = cINotificationManagerStub.getMethod("asInterface",IBinder.class);
            Object oINotificationManager = mAsInterface.invoke(null,oNotificationManagerService);
            Method mGetZenMode = cINotificationManagerStub.getMethod("getZenMode");
            zenMode = (int) mGetZenMode.invoke(oINotificationManager);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return zenMode;
    }

    // 关闭手机
    public static void shutDown() {
        try {
            Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
            Method mGetService = cServiceManager.getMethod("getService",String.class);
            Object oPowerManagerService = mGetService.invoke(null,Context.POWER_SERVICE);
            Class<?> cIPowerManagerStub = Class.forName("android.os.IPowerManager$Stub");
            Method mShutdown = cIPowerManagerStub.getMethod("shutdown",boolean.class,String.class,boolean.class);
            Method mAsInterface = cIPowerManagerStub.getMethod("asInterface",IBinder.class);
            Object oIPowerManager = mAsInterface.invoke(null,oPowerManagerService);
            mShutdown.invoke(oIPowerManager,true,null,true);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void shutdownOrReboot(final boolean shutdown, final boolean confirm) {
        try {
            Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
            // 获得ServiceManager的getService方法
            Method getService = ServiceManager.getMethod("getService", java.lang.String.class);
            // 调用getService获取RemoteService
            Object oRemoteService = getService.invoke(null, Context.POWER_SERVICE);
            // 获得IPowerManager.Stub类
            Class<?> cStub = Class.forName("android.os.IPowerManager$Stub");
            // 获得asInterface方法
            Method asInterface = cStub.getMethod("asInterface", android.os.IBinder.class);
            // 调用asInterface方法获取IPowerManager对象
            Object oIPowerManager = asInterface.invoke(null, oRemoteService);
            if (shutdown) {
                // 获得shutdown()方法
                Method shutdownMethod = oIPowerManager.getClass().getMethod(
                        "shutdown", boolean.class, String.class, boolean.class);
                // 调用shutdown()方法
                shutdownMethod.invoke(oIPowerManager, confirm, null, false);
            } else {
                // 获得reboot()方法
                Method rebootMethod = oIPowerManager.getClass().getMethod("reboot",
                        boolean.class, String.class, boolean.class);
                // 调用reboot()方法
                rebootMethod.invoke(oIPowerManager, confirm, null, false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
        try {
            // 创建对象
            ReflectClass.reflectNewInstance();

            // 反射私有的构造方法
            ReflectClass.reflectPrivateConstructor();

            // 反射私有属性
            ReflectClass.reflectPrivateField();

            // 反射私有方法
            ReflectClass.reflectPrivateMethod();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        Log.d(TAG," zenmode = " + ReflectClass.getZenMode());

result:

08-27 15:11:37.999 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectNewInstance book = Book{name='Android进阶之光', author='刘望舒'}
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateConstructor book = Book{name='Android开发艺术探索', author='任玉刚'}
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateField tag = BookTag
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateMethod string = I am declaredMethod 1 !
08-27 15:11:38.004 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectDemo:  zenmode = 0

9 注解

1)概述

Annotation(注解)是Java提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。

2)四种标准元注解

元注解的作用是负责注解其他注解。 Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
@Target修饰的对象范围 @Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

@Retention定义 被保留的时间长短 Retention 定义了该Annotation被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:  SOURCE:在源文件中有效(即源文件保留)  CLASS:在class文件中有效(即class保留)  RUNTIME:在运行时有效(即运行时保留) @Documented描述-javadoc @ Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。 @Inherited阐述了某个被标注的类型是被继承的 @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

3)示例


/1*** 定义注解*/ 
@Target(ElementType.FIELD) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
public @interface FruitProvider { 
/**供应商编号*/ 
public int id() default -1; 
/*** 供应商名称*/ 
public String name() default ""/** * 供应商地址*/ 
public String address() 
default ""; } 
//2:注解使用 public class Apple { 
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路") 
private String appleProvider; 
public void setAppleProvider(String appleProvider) { 
this.appleProvider = appleProvider; } 
public String getAppleProvider() { 
return appleProvider; } } 
/3*********** 注解处理器 ***************/ 
public class FruitInfoUtil { 
public static void getFruitInfo(Class<?> clazz) { 
String strFruitProvicer = "供应商信息:"; 
Field[] fields = clazz.getDeclaredFields();
//通过反射获取处理注解 
for (Field field : fields) { 
if (field.isAnnotationPresent(FruitProvider.class)) { 
FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class); 
//注解信息的处理地方 
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:"+ fruitProvider.address(); System.out.println(strFruitProvicer); } } } }

public class FruitRun { 
public static void main(String[] args) { 
FruitInfoUtil.getFruitInfo(Apple.class); 

/输出结果****/
// 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延 } }

10 内部类

Java类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。

1)静态内部类

定义在类内部的静态类,就是静态内部类。

public class Out { 
private static int a; 
private int b; 
public static class Inner { 
public void print() { 
System.out.println(a); } } }
  1. 静态内部类可以访问外部类所有的静态变量和方法,即使是private的也一样。
  2. 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
  3. 其它类使用静态内部类需要使用“外部类.静态内部类”方式,
    如下所示:Out.Inner inner = new Out.Inner();inner.print();
  4. Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

2)成员内部类

定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
}
}

3)局部内部类

定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。

public class Out { 
private static int a; 
private int b; 
public void test(final int c) { 
final int d = 1; 
class Inner { 
public void print() { 
System.out.println(c); } } } }

4) 匿名内部类

匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。

11 泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用Java 泛型。

1)泛型方法

// 泛型方法 
printArray public static < E > void printArray( E[] inputArray ) { 
for ( E element : inputArray ){
System.out.printf( "%s ", element ); 
} 
}

2)泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> { 
private T t; 
public void add(T t) { 
this.t = t; 
} 
public T get() { 
return t; }

注:类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。 类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

12 java序列化(创建可复用的java对象)

1 保存(持久化)对象及其状态到内存或者磁盘

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

注:== 序列化对象以字节数组保持-静态成员不保存==

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

2 序列化用户远程对象传输

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

实现方式:

1)Serializable实现序列化

在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

2)ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化 通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化。

3)writeObject 和 readObject自定义序列化策略

在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略。

注:序列化 ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化并不保存静态变量 序列化子父类说明 要想将父类对象也序列化,就需要让父类也实现Serializable 接口。

== Transient 关键字阻止该变量被序列化到文件中==

  1. 在变量声明前加上Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
  2. 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

13 java复制

将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝。所以大家知道了哈,这三种概念实际上都是为了拷贝对象。

1)直接复制赋值

直接赋值。在Java中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说a1和a2指向的是同一个对象。因此,当a1变化的时候,a2里面的成员变量也会跟着变化。

2)浅复制

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

class Resume implements Cloneable{ 
public Object clone() { 
try { 
return (Resume)super.clone(); 
} catch (Exception e) { 
e.printStackTrace(); return null; } } }

3)深复制

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

class Student implements Cloneable {
String name;
int age;
Professor p;
Student(String name, int age, Professor p) {
this.name = name;
this.age = age;
this.p = p;
}
public Object clone() {
Student o = null;
try {
o = (Student) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
o.p = (Professor) p.clone();
return o;
}
}

注:在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值