Java基础


Java基础

1.基本数据类型

1.1 8种基本类型

1.2 包装类型

将简单类型包装为类,含有以下用途

  1. 提供类的对象操作,如类型转换,进制转换等
  2. 集合不允许存放基本数据类型,使用包装类
  3. 包装类含有基本类型的相关属性,如最大值,最小值等

包装类都为final不可继承

1.3 装箱与拆箱

Integer x = 2; // 装箱 调用了 Integer.valueOf(2)
int y = x; // 拆箱 调用了 X.intValue()

new Integer(123) 和 Integer.valueOf(123) 区别
new Interger 新建了一个对象
.valueOf()会调用缓存池的对象,多次调用会取得同一对象的引用.

包装类型内存使用 private static class IntegerCache,声明一个内部使用的缓存池

如Integer中有个静态内部类IntegerCache,里面有个cache[],也就是Integer常量池,常量池的大小为一个字节(-128~127)
为啥把缓存设置为[-128,127]区间?性能和资源之间的权衡。 在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的大小。
基本类型对应的缓冲池如下:

boolean values: true and false
all byte values
short values: between -128 and 127
int values: between -128 and 127
char: in the range \u0000 to \u007F

1.4 String

String 被声明为final,不可继承,线程安全

  1. intern() 方法:
    当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;
    否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

  2. 运行效率
    StringBuilder > StringBuffer > String
    但是String str = “hello”+ “world”的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高

  3. 使用总结

少量数据: String
单线程操作,字符缓冲区大量数据: Stringbuilder
多线程操作,字符缓冲区大量数据: StringBuffer
4. 线程安全
String:对象定义后不可变,线程安全。
StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区大量数据。
StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
5. 共同点
StringBuilder和StringBuffer有共同的父类AbstractStringBuilder(抽象类)。

  StringBuilder和StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…),只是StringBuffer会在方法上加上synchronized关键字进行同步。

String a = "hello2";    
String b = "hello";       
String c = b + 2;       
System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的

String a = "hello2";    
final String b = "hello";       
String c = b + 2;       
System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问

2.关键字

2.1 final

  • 数据:: 声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能改变的常量
  • 基本类型:, 数据不变
  • 引用类型:, 是引用不变,但被引用的对象可以被改变
  • 被修饰的数据在jvm常量池中
  • 被修饰的类不能被继承,方法不能被重写

2.2 static

  • 修饰类的话只能修饰内部类:, 非静态内部类编译完成之后会隐含一个引用,指向创建他的外围类,但是静态内部类没有. 所以,静态内部类的创建不需要依赖外围类,不能使用任何外围类的非static成员变量和方法
  • 修饰成员变量和成员方法:: 被类中所有的对象共享,存放在JVM方法区(1.8为Metaspace),类加载后可直接调用,aclass.staticMethod(),aclass.staticValue
  • 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.

单例模式:

class Singleton{
    private volatile static Singleton uniqueInstance;
    private Sigleton(){};
    public static Singleton getSingleton(){
        if(uniqueInstance==null){
        synchronized(Singleton.class){
        if(uniqueInstance==null){
            uniqueInstance = new Singleton();
            }
                }
            }
    return uniqueInstance;
        }
}

2.3 super关键字

用于子类从父类访问变量和方法

class Animal{
    String color = "white";
}
class Dog extends Animal{
    String color = "black";
    void printColor(){
    System.out.println(color); //打印狗的color
    System.out.println(super.color);//打印Animal 的color
    }
}

3.面向对象

3.1 三大特性

  • 封装: 指把对象的属性封装在对象内部,不允许外部对象直接访问对象的内部信息
  1. 把对象属性私有化,对每个属性提供getter,setter方法
  2. 如果有带参的构造函数,需要再写一个不带参的构造函数
  3. 建议重写toString方法
  4. 内部细节对外部调用透明,外部调用无需修改或者关心内部实现
  • 继承: 是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的属性或新的方法,可以使用父类的功能,不能选择性继承.
    通过继承,可以提高代码的复用率,提高开发效率.
  1. 子类拥有父类的属性和方法,但是私有方法和属性无法访问,只能拥有.
  2. 子类可以拥有自己的方法,在父类的基础上拓展.
  3. 可以用自己的方法实现父类的方法?
  • 多态: 指一个对象拥有多种状态. 具体表现为父类的引用指向子类的实例.

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
abstract class Animal{
    abstract void eat();
}
class Cat extends Animal{
    public void eat(){ System.out.println("eat fish");}
}
class Dog extends Animal{
    public void eat(){System.out.println("eat bones");}
}

class DuoTaiDemo{
    public static void main(String[] args){
    function(new Cat());
    function(new Dog());
    
    Animal a = new Cat();
    a.eat();
    Cat c = (Cat)a;
    c.eat();
}
    public static void function(Animal a){
    a.eat();
    if(a instanceof Cat){Cat c = (Cat)a;c.catchMouse();}
    else if(a instanceof Dog){Dog c = (Dog)a;c.kanJia();}
    }
}

3.2 重载和重写

重载: 发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同.比如多个构造器.
重写: 发生在运行期,子类对父类允许访问的方法进行重新编写.

  1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写
public class Hero {
    public String name() {
        return "超级英雄";
    }
}
public class SuperMan extends Hero{
    @Override
    public String name() {
        return "超人";
    }
    public Hero hero() {
        return new Hero();
    }
}

public class SuperSuperMan extends SuperMan {
    public String name() {
        return "超级超级英雄";
    }

    @Override
    public SuperMan hero() {
        return new SuperMan();
    }
}

3.3 泛型

泛型提供了编译时类型安全检测机制. 本质是参数化类型,也就是说操作的数据类型被指定为一个参数.
类型擦除: 在编译期间,所有的泛型信息都会被擦掉.

  • 泛型方法
public static <E> void printArray(E() inputArray){
    for(E element: inputArray){
    System.out.println("%s",element);
    }
} //传入不同类型数组: Interger,Double 和 Charcter
 // 打印不同数组
  • 泛型类
public class Generic<T>{
    private T key;
    public Generic(T key){
    this.key = key;
    }
    public T getKey(){return key;}
}

Generic<Integer> genericInteger = new Generic<Integer>(123456);
  • 泛型接口
public interface Generic<T>{ public T method();}

//实现泛型接口,不指定类型:

class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}
Copy to clipboardErrorCopied
//实现泛型接口,指定类型:

class GeneratorImpl<T> implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

常用的通配符为: T,E,K,V,?

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个 java 类型
  • K V (key value) 分别代表 java 键值中的 Key Value
  • E (element) 代表 Element

3.4 接口和抽象类

  • 接口
    • 接口是抽象类的延伸
    • 从Java8开始,可以拥有默认的方法实现
    • 接口的字段+方法默认都是public的,且不允许定义为private或protected
    • 接口的字段默认都是static 和 final的
public class Main {
    public static void main(String[] args) {
        Person p = new Student("Xiao Ming");
        p.run();
    }
}

interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
//Xiao ming run
  • 抽象类
    • 如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
    • 抽象类和抽象方法都使用 abstract 关键字进行声明

抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

比较

  • 抽象类的目的是实现代码复用,一种模板设计的方法,可以让这些类都派生于一个抽象类。
  • 接口的设计目的是对类的行为进行约束,提供一种机制,使不同的类可以继承相同的功能。
  • 接口的字段只能是static 和final,抽象类的字段没有这些要求。
  • 接口的成员只能是public,抽象类可以有不同的访问权限。

3.5 反射

  • JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息。
  • 通过Class实例获取 class信息的方法成为反射。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

  • 当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
  • 类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。
  • 也可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 的 newInstance() 创建新的对象。
通过反射创建对象
Class clazz = Class.forName("reflection.Person");//获取 Person 类的 Class 对象
Person p = (Person)class.newInstance();//使用.newInstane 方法创建对象
Constructor c = class.getDeclaredConstructor(String.class,String.class,int.class);//获取构造方法并创建对象
Person p1 = (Person)c.newInstance("李四","男","20");//创建对象并设置属性

4.Java8新特性

4.1 streams(流)

  • Fliter

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

stringList
    .stream()
    .filter((s)->s.startsWith("a"))
    .forEach(System.out::println);
  • Sorted
stringList
    .stream()
    .sorted()
    .filter((s)->s.startsWith("a"))
    .forEach(System.out::println);

需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:

  • Map

中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

stringList
    .stream()
    .map(String::toUpperCase)
    .sorted((a,b)->b.compareTo(a))
    .forEach(System.out::println);
  • Match

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。

// 测试 Match (匹配)操作
        boolean anyStartsWithA =
                stringList
                        .stream()
                        .anyMatch((s) -> s.startsWith("a"));
        System.out.println(anyStartsWithA);      // true

        boolean allStartsWithA =
                stringList
                        .stream()
                        .allMatch((s) -> s.startsWith("a"));

        System.out.println(allStartsWithA);      // false

        boolean noneStartsWithZ =
                stringList
                        .stream()
                        .noneMatch((s) -> s.startsWith("z"));

        System.out.println(noneStartsWithZ);      // true
  • Count

计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。

//测试 Count (计数)操作
long startsWithB =
stringList
          .stream()
          .filter((s) -> s.startsWith("b"))
          .count();
System.out.println(startsWithB);    // 3
  • Reduce(规约)

这是一个 最终操作 ,允许通过指定的函数来将stream中的多个元素规约为一个元素,规约后的结果是通过Optional 接口表示的:

//测试 Reduce (规约)操作
        Optional<String> reduced =
                stringList
                        .stream()
                        .sorted()
                        .reduce((s1, s2) -> s1 + "#" + s2);

        reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2

译者注: 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于Integer sum = integers.reduce(0, (a, b) -> a+b);也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);

评论
 Previous
Java I/O Java I/O
Java I/o基础概念Java 的I/O大概可以分成一下几类: 磁盘操作:File 字节操作:InputStream 和 OutputStream 字符操作:Reader 和 Writer 对象操作:Serializable 网络操作:
2021-05-24
Next 
二叉树专项练习(三)(depth&height) 二叉树专项练习(三)(depth&height)
110. 平衡二叉树给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 输入:root = [3,9,20,null,null,15,7
  TOC