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 }
View Code

继承:

  • 可以实现已存在的类的变量,方法(非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 }
View Code

多态:

  • 对同一个行为体现出不同的表现形式,比如吃,可以吃饭,吃零食等。
  • 在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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

关于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):

  • 一个对象应该对其他对象有最少的了解。
  • 此原则与接口隔离原则可以结合使用,尽量保持一个方法一个具体行为,而不要涵盖多个行为。

(以上所有内容皆为个人笔记,如有错误之处还望指正。)

posted @ 2019-09-30 12:13  喜欢听风  阅读(184)  评论(0编辑  收藏  举报