java基础学习

1、java访问控制权限

以前对于public、private、protect、default,都只是简单的知道其对应的访问权限范围,现在看了think in java中的描述,讲了这几种访问权限在什么场景下使用,觉得进一步的了解的它存在的意义和合理性。

因为在我们的日常开发中,常常会创建一些自己常用的工具类或者相应的类库,这个时候,权限控制标识符可以使我们更好的组织和维护工具类库,以及给客户端(在这里指工具类库的调用方)对工具类库的调用更加的友好,透明。

default(friendly):即默认的包访问权限

  在 java 中,如果没有设定方法或者域的访问权限,那么他默认的访问范围,就是当前包,即当前包下的其他的类,都可以访问。

  调用方式:通过类实例(静态方法通过类调用)调用。

public:公开的访问权限,也称接口访问权限。

  对于所有的对象都可以访问,通常修复需要被外部调用的方法,即我们需要向外部暴露的接口。

  调用方式:通过类实例(静态方法通过类调用)调用。

private:私有的访问权限;只有包含该对象的类,才有访问权限。

  对于不想暴露给外部的的方法,那么,我们就可以使用 private,常常修饰 那些 服务于 暴露的外部接口的 方法,及被那些接口调用,这样,我们就可以随意的修改这个方法中的实现,而不用担心会影响到 调用方。

  调用方式:在包含类中直接调用

protect:受保护的访问权限,又称继承访问权限

  通常是父类授权给子类访问,即只有派生了父类的子类才可以访问,所以它叫继承访问权限,即必须继承,才可以访问。

  调用方式:在派生类中直接调用。

java编程思想中,建议我们在类的权限控制编写顺序如下:

  1.public、2、protect、3、default、4、private

在阅读的时候,这种方式显得更合理、更容易。

2、java反射的用途和实现

主要用途:

  反射最重要的用途就是开发各种通用框架。

程序中一般的对象类型都是在编译期就确定下来的,而Java 反射机制可以动态的创建对象并调用其属性,这样对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象即使这个对象在编译期是未知的,
反射的核心:是 JVM 在运行时 才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁。
当我们在使用 IDE(如 EclipseIDEA)时,当我们输入一个队长或者类并向调用它的属性和方法时,一按 (“.”)点号,编译器就会自动列出她的属性或方法,这里就会用到反射。

基本反射功能的实现(反射相关的类一般都在java.lang.relfect包里):

  • 获的class对象
    • 使用Class类的forName静态方法,例如:Class.forName("xxxx");
    • 直接获取某一个类的class,例如Object.class;
    • 调用某个对象的getClass()方法,例如:new Object().getClass();
  • 判断是否为某个类的实例
    • 用isstanceof关键字来判断是否为某个类的实例
  • 创建实例
    • 使用Class对象的newInstance()方法创建Class对象对应类的实例。
    • 先通过Class对象获取指定的构造器Constructor,再调用ConStructor对象的newInstance()方法创建实例。
  • 获取方法
    • getDeclareMethods()
  • 获取构造器信息
    • getConstructors()
  • 获取类的成员变量(字段)信息
    • getFields();访问共有的成员变量
    • getDeclareFields();得到所有的字段,包括公共,保护,默认(包)和私有变量,但不包括继承的字段。
  • 调用方法
    • invoke();
  • 利用反射创建数组
    • Array.newInstance()

注意:

  由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
  另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

①、获得 Class 对象
(1)、使用 Class类的 forName() 静态方法:
public static Class<?> forName(String className)
……
在JDBC开发中常用此方法加载数据库驱动:
……java
Class.forName(driver)
(2)、直接获取某一个对象的 class,比如:
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
(3)、调用某个对象的getClass() 方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
②、判断是否为某个类的实例
  public native boolean isInstance(Object obj);
③、创建实例
通过反射来生成对象主要有两种方式。
(1)使用 Class 对象的 newInstance() 方法来创建对象对应类的实例。
Class<?> c  = String.calss;
Object str = c.getInstance();
(2)、先通过 Class 对象获取制定的 Constructor 对象,在调用 Constructor 对象的 newInstance() 方法来创建实例。这种方法可以用指定的构造器构造类的实例。
  //获取String所对应的Class对象
   Class<?> c = String.class;
  //获取String类带一个String参数的构造器
  Constructor constructor = c.getConstructor(String.class);
  //根据构造器创建实例
  Object obj = constructor.newInstance("23333");
  System.out.println(obj);

 3、序列化与反序列化

序列化:Java**对象**转换为**字节序列**的过程。
反序列化:Java把**字节序列**恢复为**对象**的过程。
对象可序列化条件:
一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable 对象。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的
如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。下面是一个序列化的代码:
// Employee.java
class Employee implements Serializable {
    public String name;
    public String address;
        //该属性为不可序列化的,所以声明为短暂的
    public transient int SSN;
    public Number number;
}

3.为什么要序列化与反序列化,它的应用场景是什么以及注意事项
说白了就是这东西能干嘛用?有什么便利提供给我们。总的来说可以归结为以下几点:
(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中; 
(2)通过序列化以字节流的形式使对象在网络中进行传递和接收; 
(3)通过序列化在进程间传递对象;

注意事项:
1.序列化时,只对对象的状态进行保存,而不管对象的方法;
2.当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
3.当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
4.并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
5.声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
6.序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途:
在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
7.Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;
8.如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;

序列化与反序列化:https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html

4、泛型

泛型,即“参数化类型”,本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型只在编译阶段有效,如
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报错

泛型类:泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

一个普通的泛型类

//此处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;
    }
}

泛型接口:泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
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> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

public class StaticGenerator<T> {
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){

    }
}

在java中是”不能创建一个确切的泛型类型的数组”的。

也就是说下面的这个例子是不可以的:
List<String>[] ls = new ArrayList<String>[10];  
而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10]; 
这样也ok的
List<String>[] ls = new ArrayList[10];

 5、容器

Collection

List有序,跟插入的顺序对应,索引index;

Set和存储顺序

  • Set(Interface) 存入Set的每个元素都必须是唯一的,因为Set不保存重复的元素。加入的元素必须定义equals()方法以确保对象的唯一性。Set和Collection有完全一样的接口。Set接口不保证维护元素的次序。
  • HashSet*(默认) 为快速查找而设计的Set。存入HashSet的元素必须定义HashCode()
  • TreeSet 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口
  • LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入顺序)。于是在使用迭代器遍历Set时,结果会按元素的插入顺序显示。元素也必须定义hashCode()方法。
  • HashSet以某种神秘的顺序保存所有元素,LinkedHashSet按照元素的插入顺序保存元素,而TreeSet按照排序顺序维护元素(按照compareTo()的实现方式)

SortedSet

SortedSet中的元素可以保证处于排序状态,这时代的它可以通过在SortedSet接口中的下列方式提供附加的功能:Comparator comparator()返回Set使用的Comparator;或者返回null,表示以自然顺序。所以SortedSet的意思是“按照对象的比较函数对元素排序”,而不是“元素的插入次序”。插入顺序可以用LinkedHashSet来保存。

队列:除了并发应用中,Queue在java中仅有两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。

优先级队列:该列表排序也是通过实现Comparable而进行控制的。从一端插入,从另一端删除。

双向队列:双向队列就像一个队列,但可以在任何一端添加或者移除元素。在LinkedList中包含支持双向队列的方法,但在java标准库中没有任何显示的用于双向队列的接口。

1.HashMap*(默认) Map基于散列表实现(它取代了HashTable)。插入和查询“键值对”的开销是固定,可以通过构造器设置容量和负载因子,以调整容器的性能。
2.LinkedHashMap 类似于HashMap,但是迭代遍历时,取得“键值对”的顺序是插入顺序,或者是最少使用(LRU)的次序,只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序。
3.TreeMap 基于红黑树的实现。查看“键”或“键值对”时,它会被排序(次序由Comparable或Comparator实现)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树。
4.WeakHashMap 弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”可以被垃圾收集器回收。
5.ConcurrentHashMap 一种线程安全的Map,它不涉及同步加锁,在并发中常用。
6.对Map中使用键的要求与对Set中的元素的要求一样。任何键都必须具有一个equals()方法,如果键被用于散列Map那么必须还具有恰当的hashCode()方法,如果键被用于TreeMap,必须实现Comparable

SortedMap:使用SortedMap可以确保键处于排序状态,这使得它具有额外的功能,这些功能由SortedMap接口中的方法提供。

LinkedHashMap:为了提高速度,LinkeHashMap散列化所有的元素,但是在遍历键值对时,却又以元素插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之采用基于访问的最少使用算法,于是需要定期清理元素以节省空间的程序来说,此功能使得程序很容易实现。

散列与散列码:

在使用自己的类作为HashMap的键,必须重载hashCode()和equals(),默认的Object.equals()只是比较对象的地址。Object.hashCode()默认是使用对象的地址计算散列码。你必须同时重载hashCode()和equals(),HashMap使用equals()判断当前的键是否与表中存在的键相同。

1、正确的equals()方法必须满足5个条件: 自反性、对称性、传递性、一致性、对任何不是null的x,x.equals()一定返回false。

2、使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或自己实现的Map也可以达到此目的,但自己实现的Map不会很快,问题在于对键的查询没有按照特定顺序保存,所以只能使用最简单的线性查询,而线性查询是最慢的查询方式。散列的价值在于速度,使得查询以快速进行。散列更进一步,它将键保存在某处,以便能够很快的找到,存储一组元素最快的数据结构是数组,所以用它来保存键的信息而不是键本身,通过键对象生成一个数字将其作为数组的下标,这个数字就是散列码,可以由类覆盖的hashCode()方法生成。所以查询一个值得过程首先是计算散列码,然后使用散列码,然后使用散列码查询数组。注意,设计hashCode()时最重要的因素就是:无论何时对同一个对象调用hashCode都应该生成同样的值。

选择接口的不同实现:

容器之间的区别通常归结为什么在背后“支持”它们,所使用的接口是有什么样的数据机构实现的。ArrayList和LinkedList都实现了List接口,所以无论选择哪一个基本的List都是相同的。然而ArrayList底层由数组支持;而LInkedList底层是双向链表实现,看具体想做什么操作选择效率最高的。再比如,Set可被实现为TreeSet、HashSet、LinkedHashSet。每一种都有不同的行为:HashSet最常用,查询速度最快,LinkedHashSet保持元素插入次序,TreeSet基于TreeMap,生成一个总是处于排序状态的Set。

对list的选择:

ArrayList和LinkedList都实现了List接口,然而ArrayList底层由数组支持,无论列表的大小如何,这些访问都很快速;而LInkedList底层是双向链表实现,对于访问时间对于较大的列表将明显增加。如果需要执行大量的随机访问,链表不会是一个好的选择。ArrayList再插入时,必须创建空间并将它的所有引用向前移动,这会随着ArrayList的尺寸增加而产生高昂的代价。LinkedList只需连接新的元素而不必修改列表剩余元素。
应避免使用Vector,它只是存在于支持遗留代码的类库中。
将ArrayList作为默认首选,只要你需要使用额外的功能或者当程序的性能因为经常从表插入删除而变差时才选LinkedList。如果使用的是固定数量的元素,那么既可以选择使用背后有数组支撑的List,也可以选择真正的数组。

对Set的选择

HashSet的性能基本总是比TreeSet好,特别在添加查询元素时。TreeSet存在的唯一原因它可以维持元素的排序状态,所以当需要一个排好序的Set时,应该使用TreeSet。因为内部结构支持排序,并且因为迭代是我们更有可能的操作,用TreeSet迭代比用HashSet要快。注意,对于插入操作,LinkedHashSet比HashSet的代价更高,这是维护链表带来的额外代价。

对于Map的选择

所有的Map实现的插入操作都会随着Map尺寸的变大而明显变慢,查找的代价通常比插入要小的多。TreeMap通常比HashMap要慢,TreeMap是一种创建有序列表的方式。输的行为是:总是保证有序,并且不必进行特殊的排序。一旦填充了一个TreeMap就可以调用keySet()方法获取键的Set视图。HashMap本身就是被设计为可以快速查找键。当使用Map时,第一选择应是HashMap,只有在要求Map始终保持有序才需要使用TreeMap。LinkedHashMap比HashMap插入的代价更高,这是维护链表带来的额外代价。HashMap可以调整性能参数来提高其性能。

原文地址:https://www.cnblogs.com/wwmiert/p/11016969.html