Java基础
1、Java面向对象三大特性:封装,继承,多态
封装:
- 将现实中的客观事物封装成抽象的类。
- 对一个类中的变量,方法进行访问符修饰,以达到有些变量,方法对外开放,有些变量,方法隐藏。
- 针对第2点对应的访问修饰符有(范围从大到小):public > protected > default > private。
- 由于封装隐藏了具体实现,仅提供接口供外部调用,所以在一定程度上可以提高安全性。
- 示例代码如下:
1 package com.spring.test.vo; 2 3 /** 4 * @Author: philosopherZB 5 * @Date: 2019/9/28 6 * 将现实中客观存在的人,抽象成一个封装类Person 7 */ 8 public class Person { 9 //使用private隐藏name属性变量,仅提供get,set方法给外部调用以达到修改的目的 10 private String name; 11 12 public String getName() { 13 return name; 14 } 15 16 public void setName(String name) { 17 this.name = name; 18 } 19 }
继承:
- 可以实现已存在的类的变量,方法(非private),并可以扩展单属于自我的变量及方法。
- 继承在Java中为单继承,即一个子类只能继承一个父类(可以实现多个接口)。
- 通过继承创建的类称为“子类”,“派生类”。
- 被继承的类称为“父类”,“基类”,“超类”。
- 继承的关系是is-a,比如说老师是人,狮子是动物等。
- 继承可以降低代码的重复性,方便后续对公共行为的维护,不过同时也增加了代码的耦合度。
- 子类不继承父类的构造器,而是显示或隐式的调用(如果父类中存在不带参构造器,则子类隐式调用该构造器;否则如果父类中仅存在带参构造器,则子类需要通过super来显示调用父类带参构造器)。
- 示例代码如下:
1 package com.spring.test.service; 2 3 /** 4 * @Author: philosopherZB 5 * @Date: 2019/9/29 6 */ 7 public class Test { 8 public static void main(String[] args){ 9 Teacher teacher = new Teacher("老师"); 10 Doctor doctor = new Doctor("医生"); 11 teacher.eat(); 12 teacher.sleep(); 13 doctor.eat(); 14 doctor.sleep(); 15 } 16 } 17 18 //将现实中客观存在的人,抽象成一个封装类Person 19 class Person { 20 private String name; 21 22 public Person(String name){ 23 this.name = name; 24 } 25 public void eat(){ 26 System.out.println(name+"吃饭"); 27 } 28 public void sleep(){ 29 System.out.println(name+"睡觉"); 30 } 31 } 32 33 //老师是一个人,继承父类Person 34 class Teacher extends Person{ 35 //显示调用父类带参构造器 36 public Teacher(String name) { 37 super(name); 38 } 39 } 40 41 //医生是一个人,继承父类Person 42 class Doctor extends Person{ 43 //显示调用父类带参构造器 44 public Doctor(String name) { 45 super(name); 46 } 47 }
多态:
- 对同一个行为体现出不同的表现形式,比如吃,可以吃饭,吃零食等。
- 在Java中体现为对方法的重写以及重载,即传入参数来决定做哪一种具体的动作(重载)不同类之间同一方法不同表现(重写)。
- 重写一般为父子类之间对方法的不同操作,重载一般为同一个类中对方法的不同操作。
- 示例代码如下:
1 package com.spring.test.service; 2 3 /** 4 * @Author: philosopherZB 5 * @Date: 2019/9/29 6 */ 7 public class Test { 8 public static void main(String[] args){ 9 Teacher teacher = new Teacher("老师"); 10 teacher.eat(); 11 teacher.eat("零食"); 12 } 13 } 14 15 //将现实中客观存在的人,抽象成一个封装类Person 16 class Person { 17 private String name; 18 19 public Person(String name){ 20 this.name = name; 21 } 22 public void eat(){ 23 System.out.println(name+"吃饭"); 24 } 25 //同一类中对方法进行重载 26 public void eat(String s){ 27 System.out.println("重载吃"+s); 28 } 29 } 30 31 //老师是一个人,继承父类Person 32 class Teacher extends Person{ 33 //显示调用父类带参构造器 34 public Teacher(String name) { 35 super(name); 36 } 37 //父子类之间对方法进行重写 38 @Override 39 public void eat(){ 40 System.out.println("重写吃饭"); 41 } 42 }
2、关键字:final
final:
- 修饰类,该类将不能被继承,其中的方法也会被隐式的指定为final方法。
- 修饰方法,该方法将不能被重写。可以提高安全性,在早期的Java版本中可以提高一些效率(由于内嵌调用),不过如今private可以将方法隐式的指定为final。
- 修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
3、基本数据类型
基本数据类型(四个整数,两个浮点,一个字符,一个布尔):
- byte,占8位,1字节,默认值:0,包装类:java.lang.Byte
- short,占16位,2字节,默认值:0,包装类:java.lang.Short
- int,占32位,4字节,默认值:0,包装类:java.lang.Integer
- long,占64位,8字节,默认值:0L,包装类:java.lang.Long
- float,占32位,4字节,默认值:0.0f,包装类:java.lang.Float
- double,占64位,8字节,默认值:0.fd,包装类:java.lang.Double
- char,可以存储任何字符,包装类:java.lang.Character
- boolean,默认值:false,包装类:java.lang.Boolean
4、JDK和JRE
JDK(Java Development Kit):
- Java开发工具包,提供了Java开发环境以及运行环境(一般安装该环境,因为JDK包含了JRE)。
- 如果web程序中有使用到JSP,那么JDK还可以将JSP转换之后的Servlet进行编译。
JRE(Java Runtime Environment):
- Java运行环境,为Java的运行提供了必要的环境支持。
5、Math三个常用方法
Math三个常用方法:
- Math.ceil(),向上取整
- Math.floor(),向下取整
- Math.round(),此方法并非四舍五入,而是加0.5之后向下取整
- 示例代码如下:
1 public class Test { 2 public static void main(String[] args){ 3 System.out.println(Math.ceil(1.5));//向上取整,输出2.0 4 System.out.println(Math.floor(1.3));//向下取整,输出1.0 5 System.out.println(Math.round(-1.5));//加0.5之后向下取整,输出-1(为了加深理解可以试一下传入-1.6,看看输出什么) 6 } 7 }
6、操作字符串:String,StringBuilder,StringBuffer
String:
- 不可变的对象(也可以称之为常量),不可变的原因是String源码中用了 private final char value[];
StringBuilder:
- 可变的对象,可变的原因是StringBuilder继承自AbstractStringBuilder,而AbstractStringBuilder源码中用了 char value[];
- 线程不安全,不过效率比较高
StringBuffer:
- 可变的对象,可变的原因是StringBuffer继承自AbstractStringBuilder,而AbstractStringBuilder源码中用了 char value[];
- 线程安全,相比StringBuilder,其内部方法使用了synchronized关键字,保证线程安全。
7、String s = "Test" 和 String s = new String("Test")
String s = "Test":
- 此操作首先会去常量池检查是否存在"Test",如果存在,则直接在栈中创建一个s指向常量池中"Test"的地址(引用);如果不存在,则先在常量池中创建一个"Test",随后再在栈中创建一个s指向该"Test"的地址(引用)。
- 对于第一点中提出的常量池一般是属于运行时数据区中的方法区,而栈指的是运行时数据区中的虚拟机栈。
String s = new String("Test"):
- 此操作首先会去常量池检查是否存在"Test",如果存在,则到堆中直接创建一个"Test",再在栈中创建一个s指向堆中"Test"的地址(引用);如果不存在,则先在常量池中创建一个"Test",随后到堆中创建一个"Test",最后再在栈中创建一个s指向堆中"Test"的地址(引用)。
- 对于第一点中提出的常量池一般是属于运行时数据区中的方法区,而栈指的是运行时数据区中的虚拟机栈,堆指的是运行时数据区中的堆。
- new操作每次都会在堆中创建一个新对象。
总结:
- 对于常量池而言,始终都只存在一个相同的"Test",即多个String用==比较都会返回true,因为地址(引用)一样。
- 对于堆而言,每次操作都会创建一个新的对象,即多个new String用==比较都会返回false,因为地址(引用)不一样。
- 示例代码如下:
1 public class Test { 2 public static void main(String[] args){ 3 String s1 = "Test"; 4 String s2 = "Test"; 5 String s3 = new String("Test"); 6 String s4 = new String("Test"); 7 System.out.println(s1==s2);//true 8 System.out.println(s3==s4);//false 9 } 10 }
8、值传递,引用传递
值传递:
- 值传递不会改变实际参数的内容。
- 基本类型是值传递,String以及基本类型包装类同样是值传递。
引用传递:
- 引用传递会改变实际参数的内容。
- 引用传递不会改变实际参数的参考地址。
总结:
- 如果创建的内容保存在了常量池中,那么是值传递;如果保存在了堆中,则是引用传递(个人理解)。
- 示例代码如下:
1 public class Test { 2 public static void main(String[] args){ 3 //值传递 4 String s1 = "Test--1"; 5 change(s1); 6 System.out.println(s1);//输出Test--1 7 //引用传递 8 StringBuilder sb = new StringBuilder("str--1"); 9 change2(sb); 10 System.out.println(sb);//输出str--1--2 11 } 12 private static void change(String str){ 13 str = "Test--2"; 14 } 15 private static void change2(StringBuilder str){ 16 str.append("--2"); 17 } 18 }
9、= = 和 equels
= =:
- 对于基本类型,基本类型包装类,String而言是比较值是否相等(其实本质上还是比较的引用是否相等,因为对于基本类型而言,他所创建的值是存在了常量池,而常量池不允许出现重复的值,对应栈中指向常量池的地址(引用)都是同一个(个人理解))。
- 对于引用类型,比较的是引用是否相等。
equels:
- 其底层是用= =来实现的,本质上仍然是比较地址(引用)是否相等。
- 大部分类都会自己重写equels方法,实现能够比较值是否相等,如String,Integer等。
关于Integer t1 = 128,Integer t2 = 128,t1==t2,返回false原因:
- Integer会缓存[-128,127]中的数,如果在这之中,则会返回true,否则,将会在堆中重新创建新的对象,结果自然返回false。
- 示例代码如下:
1 public class Test { 2 public static void main(String[] args){ 3 Integer t1 = 127; 4 Integer t2 = 127; 5 Integer t3 = 128; 6 Integer t4 = 128; 7 System.out.println(t1==t2); //true 8 System.out.println(t3==t4); //false 9 } 10 } 11 //以下结果来自反编译,可以看到使用的是Integer.valueOf()方法进行自动装箱 12 public class Test 13 { 14 public static void main(String[] args) { 15 Integer t1 = Integer.valueOf(128); 16 Integer t2 = Integer.valueOf(128); 17 System.out.println((t1 == t2)); 18 } 19 } 20 21 //以下源码来自于jdk8中的Integer类 22 public static Integer valueOf(int i) { 23 //如果在-128到127之间,则直接返回缓存中的数值,否则创建一个新的对象保存 24 if (i >= IntegerCache.low && i <= IntegerCache.high) 25 return IntegerCache.cache[i + (-IntegerCache.low)]; 26 return new Integer(i); 27 } 28 29 private static class IntegerCache { 30 static final int low = -128; //最小值为-128 31 static final int high; //最大值 32 static final Integer cache[]; //缓存存储数组 33 34 static { 35 // high value may be configured by property--最大值可以通过属性配置 36 int h = 127; //最大值127 37 String integerCacheHighPropValue = 38 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); 39 if (integerCacheHighPropValue != null) { 40 try { 41 int i = parseInt(integerCacheHighPropValue); 42 i = Math.max(i, 127); 43 // Maximum array size is Integer.MAX_VALUE 44 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); 45 } catch( NumberFormatException nfe) { 46 // If the property cannot be parsed into an int, ignore it. 47 } 48 } 49 high = h; 50 51 cache = new Integer[(high - low) + 1]; //缓存数组大小为256 52 int j = low; //得到最小值-128 53 //从-128开始循环递增把对应的数值加入到缓存数组中 54 for(int k = 0; k < cache.length; k++) 55 cache[k] = new Integer(j++); 56 57 // range [-128, 127] must be interned (JLS7 5.1.7) 58 //校验缓存最大值是否大于127 59 assert IntegerCache.high >= 127; 60 } 61 62 private IntegerCache() {} 63 }
关于hashCode 和 equels:
- equels返回true,则hashCode必然相等。
- hashCode相等,equels却不一定返回true(因为存在hash碰撞)。
10、接口,抽象类
接口:
- 接口中的方法对应的默认访问修饰符为:public
- 对于接口的实现关键字为:interface,其他类需要通过:implements来实现接口
- 接口不允许有非抽象方法的实现(jdk8支持静态方法,可以直接通过接口名.方法名调用)。
抽象类:
- 抽象类中的方法对应的访问修饰符是任意的。
- 对于抽象类的实现关键字为:abstract,其他类需要通过:extends来继承抽象类
- 抽象类允许有非抽象方法的实现。
- 抽象类有构造方法,也有main方法,并且可以直接运行。
11、面向对象六大基本原则
单一职责原则(Single Responsibility Priciple):
- 对于一个类而言,应该只有一个引起他变化的原因。
- 比如,不要将teacher,doctor都放在一个person类中,而是将其拆成teacher类,doctor类。
- 能够一定程度上降低耦合度。
开闭原则(Open Close Principle):
- 对于扩展是开放的,对于修改是关闭的。
- 一般来说就是不要修改之前的代码,可以选择继承或者实现接口来扩展对应的功能。
里式替换原则(Liskov Substitution Principle):
- 所有引用父类的地方都可以透明的使用其子类对象。
- 即父类出现的地方,把父类换成子类不会出现异常或错误;反之,子类出现的地方,把子类换成父类可能会出现异常或错误。
- 比如,person类具有eat功能,那么将person.eat换成teacher.eat是没有问题的;而如果teacher类具有独特的teach功能,那么如果将teacher.teach换成person.teach就会出现错误。
依赖倒置原则(Dependence Inversion Principle):
- 高层模块不依赖低层模块,而是应该依赖其抽象
- 抽象不依赖于细节,细节依赖于抽象
- 比如teacher类中应该尽量做一些老师共有的抽象行为,比如教书,这样后面的语文老师,数学老师等可以继承该抽象老师类进行扩展。
接口隔离原则(Interface Segregation Principle):
- 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
- 尽量一个接口只实现一个功能,如果接口功能太多可进行拆分。
迪米特原则(Law of Demeter or Least Knowlegde Principle):
- 一个对象应该对其他对象有最少的了解。
- 此原则与接口隔离原则可以结合使用,尽量保持一个方法一个具体行为,而不要涵盖多个行为。
(以上所有内容皆为个人笔记,如有错误之处还望指正。)