• Java基础知识


    一、标识符规范

    标识符必须以字母(汉字)、下划线、美元符号开头,其他部分可以是字母、下划线、美元符号,数字的任意组合。谨记不能以数字开头。java使用unicode字符集,汉字也可以用该字符集表示。因此汉字也可以用作变量名。
    关键字不能用作标识符。
    类名首字母需大写(Welcome)、方法和变量名遵循驼峰原则 snRsfService()

    二、数据类型

    java的数据类型由8种基本数据类型和多种引用数据类型构成。

    1. 基本数据类型

    1.1数值型

    数值型又分为整数类型和浮点类型。

    1. long l = 10000000000L; //整型数据默认是int, 转成long后面要加 L 或 l
    2. float f = 3.14F; //浮点常量默认是double,改成float后面要加F 或 f

      <一些可有可无的科普:计算机底层浮点数表示方法>
      在计算机底层float和double都是由IEEE754标准进行储存的,该标准将浮点数分成符号位、指数位、尾数位。分配如下:
    单精度浮点数float:32bit(4字节)
    1bit(符号位)
    8bits(指数位) 
    23bits(尾数位)

    双精度浮点数double:64bit(8字节)
    1bit(符号位)
    11bits(指数位)
    52bits(尾数位)
    数据的表示范围是由指数的位数来决定的。float的指数位有8位,能表示0-255一共256个状态。加之IEEE标准规定有127的偏移量,float的指数最大值为128。float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double范围的计算方法和float同理。double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。
    精确度是由尾数的位数来决定的。float的尾数位有23位,能表示最大7位十进制数字,但实际精度取决于浮点数的具体值。double的尾数位有52位,能表示最大16位十进制数字,但实际精度同样取决于浮点数的具体值。
    (尽量不要在比较和金额计算中使用浮点数,精确计算用BigDecimal)

    1.2 字符型

    内存中占2个字节,unicode就是用char表示的

    1.3 布尔型

    在内存中占1 或4个字节(JVM用int表示bool,CPU层面有利于高效存取)

    2. 引用数据类型

    类(Class):自定义的类。类是对象的模板,它定义了对象的属性和方法。
    接口(Interface):一种完全抽象的类,它只包含常量和方法的声明,没有实现。接口可以被类实现,以实现特定的行为。
    数组(Array):一种数据结构,用于存储固定大小的同类型元素集合。
    字符串(String):一种特殊的对象,用于表示文本数据。Java中的字符串是不可变的,一旦创建就不能修改其内容。
    包装类(Wrapper Classes):基本数据类型的封装类,如Integer、Double、Character等。它们提供了基本数据类型到对象类型的桥梁,并提供了额外的功能。
    其他自定义对象:除了上述提到的类型外,还包括用户自定义的类、枚举(Enum)、注解(Annotation)等。

    三、关键字

    1. static

    static修饰变量:静态变量又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份

    static修饰函数:静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。

    static修饰代码块:静态代码块在类构造函数之前被执行。

    2. final

    final修饰变量:修饰基本数据类型时,表示这个变量值不可变。

    final修饰引用数据类型:表示这个变量引用不可变,但引用的对象具体内容是可以改变的(比如对象的某个属性值是可变的)。

    final修饰方法:表示此方法不可被子类重写。

    final修饰类:表示此类不可以被继承。

    3. this

    this的本质就是当前对象的地址。普通方法中,this指向调用该方法的对象。构造方法中,指向正要初始化的对象。

    1. class User{
    2. int id;
    3. String name;
    4. String pwd;
    5. User(){}
    6. public User(int id, String name){
    7. this.id = id;
    8. this.name = name;
    9. }
    10. public User(int id, String name, String pwd){
    11. this(id, name);//构造方法重载,该种调用必须位于构造方法的第一行
    12. this.pwd = pwd;
    13. }
    14. }

     4. super

    super可以看做父类对象的引用,但是创建子类对象的过程并没有产生父类对象,super代表当前对象的父类型特征。在一个类中,如果构造方法的第一行代码没有显式的使用super()或this(),那么java都会默认调用super()

    5. String StringBuilder StringBuffer

    String表示字符串,从jdk1.0开始就出现了。从Java7版本开始存在于堆中的字符串常量池中。其底层使用一个数组进行存放,在JAVA8中是一个char数组,而在JAVA9中是一个byte数组,并且这个数组使用final进行修饰,表示不可变的。所以String类是天生线程安全的。

    StringBuffer是为了解决String类在内容频繁变化时,性能不高这个问题。表示的是一个可变长度字符串,他也是在Jdk1.0中就出现了,其内部的方法都是被Synchronized修饰,所以他是线程安全的。并且在stringBuffer有个缓存数组一直缓存变化着的内容(缓存最后一个值)。它的toString方法的返回值就是这个缓存数组的String对象。这一点和stringBuilder不一样,stringbuilder的tostring返回值是通过存储他的那个数组创建的。

    虽然说这一点不同StringBuilder也是表示可变长度字符串。他是从JDK1.5开始出现的,其方法没有被Synchronized修饰,是非线程安全的,但它的性能相对于StringBuilder更高。

    四、方法与对象

    1. 方法重载的条件

    形参类型,个数,顺序不同即可构成方法重载。只有返回值不同不构成方法重载,会报编译错误。

    2. 对象实例化的方式

    2.1 new

    2.2 clone()

    2.3 通过反射机制创建

    1. //用 Class.forName方法获取类,在调用类的newinstance()方法
    2. Class cls = Class.forName("com.dao.User");
    3. User u = (User)cls.newInstance();

    2.4 序列化反序列化

    1. //将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用)
    2. ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
    3. //序列化对象
    4. out.writeObject(user1);
    5. out.close();
    6. //反序列化对象
    7. ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
    8. User user2 = (User) in.readObject();
    9. System.out.println("反序列化user:" + user2);
    10. in.close();

     3. 对象复制的方式

    通过构造方法、通过对象的值、通过object的clone方法。

    1. //通过构造方法
    2. TestClone tc = new TestClone();
    3. tc.name = 123;
    4. TestClone tc2 = new TestClone(tc);
    5. //通过对象的值
    6. 直接将老对象的值挨个复制给新对象即可
    7. //通过object的clone方法
    8. 通过该方式复制对象时必须重写cloneable的clone方法。

     4. 深拷贝与浅拷贝的区别

    浅拷贝:仅仅克隆基本类型变量,不克隆引用类型变量;

    深拷贝:既克隆基本类型变量,又克隆引用类型变量;

    五、面向对象特性

    1. 封装

    将对象的属性包裹起来,并通过接口控制访问。高内聚低耦合,并隐藏实现细节,安全性高。

    2. 继承

    java类没有多继承,即子类只能继承一个父类,接口有多继承。子类继承父类可以得到除构造方法外的全部非私有属性和方法。java.lang.Object是所有类的父类。

    1. //instanceof 判断对象是否是某个类的实例对象
    2. Stu stu = new Stu();
    3. System.out.print(stu instanceof Stu);

    3. 多态

    同一个方法调用,由于对象不同,可能有不同的行为。多态是方法的多态不是属性的多态。优点是简化灵活,替换性强,可拓展性高。 

    三个必要条件是重写,继承和父类引用指向子类对象。

    当指向子类对象的父类引用,调用子类重写的父类方法时,多态就出现了。

    1. public class Test {
    2. public static void main(String[] args) {
    3. show(new Cat()); // 以 Cat 对象调用 show 方法
    4. show(new Dog()); // 以 Dog 对象调用 show 方法
    5. Animal a = new Cat(); // 向上转型
    6. a.eat(); // 调用的是 Cat 的 eat
    7. Cat c = (Cat)a; // 向下转型
    8. c.work(); // 调用的是 Cat 的 work
    9. }
    10. public static void show(Animal a) {
    11. a.eat();
    12. // 类型判断
    13. if (a instanceof Cat) { // 猫做的事情
    14. Cat c = (Cat)a;
    15. c.work();
    16. } else if (a instanceof Dog) { // 狗做的事情
    17. Dog c = (Dog)a;
    18. c.work();
    19. }
    20. }
    21. }
    22. abstract class Animal {
    23. abstract void eat();
    24. }
    25. class Cat extends Animal {
    26. public void eat() {
    27. System.out.println("吃鱼");
    28. }
    29. public void work() {
    30. System.out.println("抓老鼠");
    31. }
    32. }
    33. class Dog extends Animal {
    34. public void eat() {
    35. System.out.println("吃骨头");
    36. }
    37. public void work() {
    38. System.out.println("看家");
    39. }
    40. }

    六、抽象类

    使用abstract修饰的类是抽象类,包含抽象方法的类是抽象类。 

    1. //模板方法模式(抽象类的应用)
    2. public abstract class DBOperator {
    3. //1. 建立连接 2. 打开数据库 3. 使用数据库 4. 关闭连接
    4. public abstract void connection();
    5. public void open(){
    6. System.out.println("打开数据库");
    7. }
    8. public void use(){
    9. System.out.println("使用数据库");
    10. }
    11. public void close(){
    12. System.out.println("关闭连接");
    13. }
    14. public void process(){
    15. connection();
    16. open();
    17. use();
    18. close();
    19. }
    20. public static void main(String[] args) {
    21. // new MySqlOperator().process();
    22. new OracleOperator().process();
    23. }
    24. }
    25. class MySqlOperator extends DBOperator {
    26. @Override
    27. public void connection() {
    28. System.out.println("建立和Mysql数据库的连接");
    29. }
    30. }
    31. class OracleOperator extends DBOperator {
    32. @Override
    33. public void connection() {
    34. System.out.println("建立和Oracle数据库的连接");
    35. }
    36. }

    七、接口

    由全局常量(public static final 可省略)、抽象方法(public abstract)、默认方法(public default)、静态方法(public static)组成,其中方法的public可省略。java9中接口可以包含只能被接口内部调用的私有方法。
    常量:接口中可以定义常量,这些常量默认是public static final的,即它们是公共的、静态的、不可变的。接口不能包含变量。
    抽象方法:接口中可以包含抽象方法,这些方法只有方法签名,没有方法体。抽象方法默认是public abstract的,即它们是公共的、抽象的。实现接口的类必须提供接口中所有抽象方法的具体实现。
    默认方法:这些方法有方法体,因此可以提供默认实现。可以被重写。
    静态方法:这些方法有方法体,可以直接通过接口名调用,而不需要实现类的实例。静态方法使用static关键字定义,实现类不能重写接口的静态方法。静态方法只能调用接口中的其他静态方法或私有静态方法。
    命名冲突问题:
    对于一个实现了多个接口且继承自某一个父类的子类,如果方法名和形参列表相同,会发生命名冲突。若父类的方法和接口默认方法名冲突,则父类优先。若多个接口默认方法导致命名冲突,则子类需重写该方法。

    八、内部类

    为了方便使用外部类相关的属性和方法,通常会在开发中定义一个内部类。

    内部类可以直接访问外部类的私有属性,但外部类不能访问内部类的属性。内部类被当成外部类的成员。SubList就是ArrayList的内部类。

    1. //非静态内部类初始化
    2. Outer.Inner inner = new Outer().new Inner();
    3. //静态内部类初始化
    4. Outer.Inner inner = new Outer.Inner();
    5. //匿名内部类,适合只使用一次的类
    6. /**
    7. 目标:掌握匿名内部类的使用形式
    8. */
    9. public class Test {
    10. public static void main(String[] args) {
    11. //使用匿名内部类替代学生类,创建的类型是new的那个类的子类
    12. Swimming s = new Swimming() {
    13. @Override
    14. public void Swim() {
    15. System.out.println("学生游的很快");
    16. }
    17. };
    18. go(s);
    19. }
    20. /**
    21. 方法:学生、老师、运动员可以一起游泳
    22. */
    23. public static void go(Swimming s)
    24. {
    25. System.out.println("开始游泳了~~");
    26. s.Swim();
    27. System.out.println("结束游泳了~~");
    28. }
    29. }
    30. //创建学生类
    31. /*class Student implements Swimming{
    32. @Override
    33. public void Swim() {
    34. System.out.println("学生游的很快");
    35. }
    36. }*/
    37. //定义一个接口规范游泳
    38. interface Swimming{
    39. void Swim();
    40. }

    九、包装类

    包装类与基本数据类型的转换过程就是java的自动装箱与拆箱,在性能敏感的应用中,过度使用会导致性能下降,因为需要额外的内存分配和垃圾回收。

    1. Integer ele = new Integer(10);
    2. int ele3 = ele.intValue();
    3. //自动装箱
    4. Integer ele2 = 10;
    5. //自动拆箱
    6. int ele4= ele;
    7. //Integer包装类缓存范围[-128,127]
    8. Integer a = 100;
    9. Integer b = 100;
    10. System.out.println(a==b);//true
    11. Integer a1 = 200;
    12. Integer b1 = 200;
    13. System.out.println(a1==b1);//false

    <一些不必要的知识点:包装类默认缓存范围>
    Boolean:true和false
    Integer:-128~127
    Byte:-128~127
    Character:0~127
    Short:-128~127
    Long:-128~127
    Float和Double无缓存

    十、异常机制

    java中的异常都继承自Throwable类,分为Error和Exception。Error表示编译或系统错误,如OOM和StackOverFlow。Exception表示程序可以捕获或处理的异常,又分为运行时异常和非运行时异常。

    RuntimeException 是运行时异常,这类异常是不受检查的,需要程序员自己决定要不要做处理。RuntimeException 以外的异常都属于非运行时异常。例如 IOException、ClassCastException 等以及用户自定义的 Exception 异常等。

    <一些不必要的知识点:try-cache-finally 与 try-with-resouce>

    前者中的finally一定会被运行,但遇见如下情况不会执行:比如退出程序,finally 语句块中发生了异常,还有程序所在的线程死亡。try-with-resouce可以保证运行结束后每个资源都被自动关闭。任何实现了 java.lang.AutoCloseable 的对象, 包括所有实现了 java.io.Closeable 的资源对象, 都可以用它进行关闭。

    1. public class MyAutoClosable implements AutoCloseable {
    2. public void doIt() {
    3. System.out.println("MyAutoClosable doing it!");
    4. }
    5. @Override
    6. public void close() throws Exception {
    7. System.out.println("MyAutoClosable closed!");
    8. }
    9. public static void main(String[] args) {
    10. try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
    11. myAutoClosable.doIt();
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    十一、泛型

    JDK 1.5开始引入Java泛型(generics)这个特性,该特性提供了编译时类型安全检测机制,允许程序员在编译时检测到非法的类型。
    泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    泛型优点有四个,1保证类型安全,2类型自动转换,消除强转,3 避免装箱、拆箱,提高性能,.4 提升程序可复用性。 为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”,将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型)。

    1.  优点

    1.1 类型安全

    泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了,而不是在运行时当作ClassCastException展示出来。

    1. //没有泛型的情况
    2. ArrayList list = new ArrayList<>();
    3. list.add("11");
    4. list.add(123);//编译正常
    5. //有泛型的情况
    6. List list = new ArrayList<>();
    7. list.add("11");
    8. list.add(123);//编译报错

    1.2 消除强转

    1. //没有泛型的代码段需要强制转换
    2. public static void main(String[] args) {
    3. List list = new ArrayList();
    4. list.add(123);
    5. Integer integer = (Integer) list.get(0);
    6. }
    7. //有泛型的代码段不需要强制转换
    8. public static void main(String[] args) {
    9. List list = new ArrayList();
    10. list.add(1);
    11. int s = list.get(0);
    12. }

    1.3 更高的运行效率

    避免了不必要的装箱、拆箱操作,提高程序的性能。在非泛型编程中,将简单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都具有很大开销。引入泛型后,就不必进行Boxing和Unboxing操作了。

    1. //使用泛型前
    2. object a=1;//由于是object类型,会自动进行装箱操作。
    3. int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。
    4. //使用泛型后
    5. public static T GetValue(T a) {
    6.   return a;
    7. }
    8. public static void Main(){
    9.   int b=GetValue<int>(1);//使用这个方法的时候已经指定了类型是int,所以不会有装箱和拆箱的操作。
    10. }

    1.4 提高程序的可复用性

    如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法。

    1. private static extends Number> double add(T a, T b) {
    2. System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
    3. return a.doubleValue() + b.doubleValue();
    4. }

    2. 泛型的使用

    2.1 泛型类

    1. public class Result {
    2. int code;
    3. String message;
    4. T data;
    5. }

    2.2 泛型接口

    1. @FunctionalInterface
    2. public interface MyInterFace{
    3. T apply(V val);
    4. }

    2.3 泛型方法

    1. public static extends String, E extends String> K myFuc(E input){
    2. System.out.println("input = " + input);
    3. return (K) "";
    4. }
    5. public static void processElements(Listsuper T> elements) {
    6. for (Object element : elements) {
    7. System.out.println(element);
    8. }
    9. }

    2.4 泛型通配符

    2.4.1 “?”无边界的通配符,使用精确的参数类型

    2.4.2 "extends"关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

    2.4.3 "super"关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

    <常见泛型>
    T:任意类型 type
    E:集合中元素的类型 element
    K:key-value形式 key
    V: key-value形式 value
    N: Number(数值类型)
    ?: 表示不确定的java类型

    3. 类型擦除

    3.1 举个例子

    1. List list1= new ArrayList<>();
    2. List list2= new ArrayList<>();
    3. System.out.println(list1.getClass()==list2.getClass());

    两秒钟,猜猜看输出什么?

    1----

    2----

    输出true

    为什么?因为list1 和 list2 的运行时类都是 ArrayList,即泛型在代码运行时被擦除了。

    3.2 为什么进行类型擦除

    主要原因是为了保持与旧版Java代码的兼容性,并实现泛型的逐步引入,而不会对JVM或Java类库进行大规模的修改。另一个好处是简化了Java泛型的实现。由于类型信息在编译时被擦除,JVM不需要在运行时处理泛型类型参数,这有助于保持JVM的简洁性和性能。

    3.3 类型擦除原则

    消除类型参数声明,即删除<>及其包围的部分。

    根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。

    为了保证类型安全,必要时插入强制类型转换代码。

    自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。

    3.4 桥接方法

    1. public interface Fruit {
    2. T get(T param);
    3. }
    4. public class Apple implements Fruit {
    5. @Override
    6. public Integer get(Integer param) {
    7. return param;
    8. }

    按照之前我们的理解,在进行类型擦除后,应该是这样的:

    1. public interface Fruit {
    2. Object get(Object param);
    3. }

    但是,如果真是这样的话那么代码是无法运行的,因为Apple类中get方法,与接口中的方法参数不一致,也就是说没有覆盖接口中的方法。
    针对这种情况,编译器会通过添加下面桥接方法来满足语法上的要求,同时保证了基于泛型的多态能够有效。

    1. public Integer get(Integer param) {
    2.    return param;
    3. }

  • 相关阅读:
    Spark基础【完善案例一、框架式开发模式再回顾】
    安卓APP源码和设计报告——体育馆预约系统
    java算法题Day68
    记录工作过程中一次业务优化
    Kubernetes容器状态探测的艺术
    云耀服务器L实例部署Discuz!Q论坛|华为云云耀云服务器L实例评测使用体验
    基于matlab的FP-MAP球形检测算法误码率仿真
    Flutter笔记:发布一个模块 scale_design - (移动端)设计师尺寸适配工具
    mybatis mapper.xml 文件外键映射
    使用PaddleNLP UIE模型提取上市公司PDF公告关键信息
  • 原文地址:https://blog.csdn.net/weixin_43681666/article/details/136138495